Instrukcje SSE2, przesunięcie bitowe _mm_srl_epi64

0

Potrzebuję przesunąć w prawo liczbę typu __m128i i używam do tego:

https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=193,194,5504,6754,6754&techs=SSE2&cats=Shift

Co prawda _mm_srl_epi64 nie potrafi przesunąć liczby 128 bitowej, przesuwa dwie liczby 64 bitowe, które są w __m128i. To mógłbym zrobić bez tych instrukcji. Niemniej zastanawia mnie coś innego. Jako drugi argument _mm_srl_epi64 przyjmuje __m128i count. I o liczbę bitów równą count przesuwamy pierwszą liczbę __m128i a. Ale po co count ma być aż 128-bitową liczbą? Maksymalnie i tak możemy przesunąć liczbę o 64 bity. Wpisywanie count większego niż 64 nie ma sensu. Tymczasem oni chcą by count był liczbą __m128i. Jaki to ma sens? A może jest coś czego nie rozumiem w całym tym przesunięciu i ma to swoje uzasadnienie?

2

__m128i _mm_srl_epi64 (__m128i a, __m128i count)
#include <emmintrin.h>
Instruction: psrlq xmm, xmm
CPUID Flags: SSE2
Description
Shift packed 64-bit integers in a right by count while shifting in zeros, and store the results in dst.
Operation

FOR j := 0 to 1
	i := j*64
	IF count[63:0] > 63
		dst[i+63:i] := 0
	ELSE
		dst[i+63:i] := ZeroExtend64(a[i+63:i] >> count[63:0])
	FI
ENDFOR

jak widać, tylko dolne 64 bity count są brane pod uwagę. jeśli tworzą one liczbę 64 lub większą to wynik jest zerem.

Wpisywanie count większego niż 64 nie ma sensu.

jeśli to stała to faktycznie nie ma sensu, ale jeśli jest wynikiem obliczeń to może mieć sens. po co samemu obcinać wynik, skoro można polegać na instrukcji, która radzi sobie z dziwnymi przesunięciami?

1
Tomasz_D napisał(a):

Tymczasem oni chcą by count był liczbą __m128i. Jaki to ma sens? A może jest coś czego nie rozumiem w całym tym przesunięciu i ma to swoje uzasadnienie?

A co jeżeli chcesz przesunąć 4096 bitową liczbę którą trzymasz w kilku kawałkach po 128 bit o 1023 bity, to co masz szukać innej biblioteki?

1

Jestem przeciwnikiem używania jakiś dziwnych rozszerzeń i mikro optymalizacji bez pomiarów.
A jak już to lepiej najpierw napisać testy performance napisać kod normalnie i potem porównywać wyniki jak kod jest zmieniony z użyciem rozszerzenia.

Ergo zacząłbym od gotowych rozwiązań jak boost::multiprecision.
Zobaczył jaki kod generuje kompilator z użyciem zaawansowanych opcji kompilatora.
Napisałby jakiś test performance. Zrobił pomiar.
A dopiero potem eksperymentował z rozszerzeniami i porównywał wyniki testów performance, żeby wiedzieć na pewno, że gra jest warta ścieżki.

0

@MarekR22: rozumiem, że zmierzasz do tego, iż po prostu czasami kod wcale nie będzie szybszy?

Cóż. Po pierwsze nie potrafię zrobić pomiarów performance kodu, o co rozbiłem się w tym wątku:

https://4programmers.net/Forum/C_i_C++/359812-pomiary_predkosci_kilku_prng_w_c?p=1835673#id1835673

I co wyjaśnił mi też alagner w prywatnej rozmowie, gdy jeszcze miał konto (tj. jak nietrywialne zadanie to może być). Zrobienie takich pomiarów:

https://prng.di.unimi.it

Jest po prostu trudne (nie mówię o testowaniu wszystkich PRNG tam zamieszczonych, maks. dwóch, choć nie ma to znaczenia).

Po drugie mnożę liczby liczby 128-bitowe w GF(2^128), tak jak opisałem to tutaj:

https://4programmers.net/Forum/C_i_C++/360958-mnozenie_carry_less_liczb_128_bitowych_i_instrukcje_pclmulqdq?p=1845385#id1845385

Wiec i tak używam CLMUL instruction set i działam na __m128i. Tylko dlatego muszę korzystać z przesunięcia bitowego _mm_srl_epi64. To mnożenie jest używane w AES w trybie GCM, właśnie z tymi instrukcjami, więc nie ma raczej szans, aby zwykły kod mógł być szybszy, bo inaczej nikt nie używałby GCM z instrukcjami (choć tam liczby się też bezpieczeństwo kryptograficzne, a nie wiem, czy one nie wykonują się w stałym czasie, wówczas może jest miejsce dla jakichś optymalizacji, tyle, że ja też chcę, aby one mi się wykonywały w stałym czasie).

3

Chcę powiedzieć, że autorzy kompilatorów to naprawdę mądrzy ludzie i potrafią zaskakujące rzeczy.
Dzięki nim kompilator potrafi naprawdę dużo, a wielu developerów po prostu nie jest świadomych, że sama zmiana ustawień kompilatora może im dać za darmo duża poprawę wydajności.
Np jeśli kod komilujesz tylko lokalnego użytku to opcja -march=native jest bardzo wskazana i kompilator wygeneruje kod, który będzie zoptymalizowany pod twój konkretny procesor.

Sięganie po jakieś mikrooptymalizacje jest zwodnicze i często daje skutek odwrotny.
Dlatego jak walczy się o wydajność kodu, to pomiary są najważniejsze, by miec pewność, że faktycznie uzyskuje się poprawę.
Optymalizacje procesora i kompilatora, potrafią dziwne rzeczy i nawet najlepsi deweloperzy często są zaskakiwani wynikami i nie potrafią wyjaśnić otrzymanych wyników.
Równocześnie napisanie dobrych benchmarków jest w C++ naprawdę trudne, bo zasada As-if potrafi wyciąć testowany kod i nie widać tego w oczywisty sposób w wynikach. Niestety łatwo napisać benchmark, który nie mierzy tego co jest oczekiwane.

1

Prezentacja omawiająca to, co wyżej — kompilatory są bardzo sprytne, i co do zasady, lepsze rezultaty osiąga się lepiej im „tłumacząc” kod, niż klepiąc swojego asma. Przede wszystkim pod względem czytelności, ale i często pod względem wydajnościowym.

1
MarekR22 napisał(a):

Chcę powiedzieć, że autorzy kompilatorów to naprawdę mądrzy ludzie i potrafią zaskakujące rzeczy.

Ci autorzy musieli najpierw zdobyć doświadczenie w pisaniu mikro-zoptymalizowanych algorytmów, włączając w to intrinsics i assemblera.
Dłubiąc przy instrukcjach zdobywa się wiedzę i doświadczenie. Również wiedzę o tym co daje, a co nie daje istotnego zwiększenia wydajności.

1 użytkowników online, w tym zalogowanych: 0, gości: 1