2016-09-01 14 views
6

Instrukcje SHLD/SHRD to instrukcje montażu służące do wprowadzania przesunięć wieloetapowych.Wersje SIMD instrukcji SHLD/SHRD

Rozważmy następujący problem:

uint64_t array[4] = {/*something*/}; 
left_shift(array, 172); 
right_shift(array, 172); 

Co jest najbardziej wydajnym sposobem wdrożenia left_shift i right_shift, dwie funkcje, które działa w oparciu o przesunięcie na tablicy czterech 64-bitowych bez znaku liczby całkowitej, jakby to była wielka 256 bitów bez znaku liczby całkowitej?

Czy jest to najskuteczniejszy sposób na wykonanie instrukcji SHLD/SHRD lub czy istnieją lepsze (jak wersje SIMD) instrukcje dotyczące nowoczesnej architektury?

+0

Dla jakiej architektury programujesz? Jeśli jesteś na x86, możesz mieć instrukcje do SSE3 [edytuj: jak @Ruslan zwrócił uwagę, że możesz mieć obsługę AVX/AVX2 w trybie 32-bitowym], lub na x86_64 do AVX2 (chyba że masz dużo szczęścia i dostaniesz się do program dla AVX512 na dużym koprocesorze Intela). Jeśli korzystasz z ARM i masz wsparcie NEON-a, istnieją również instrukcje zmiany SIMD. – Dalton

+1

Zależy, czy ustalono "172", czy tylko przykładową wartość: jako 172 wynosi 21,5 bajta, co pozwala na zapamiętanie zawartości przez 21 bajtów, a następnie przesunięcie 11 bajtów docelowych 4 razy w prawo (np. 3x 'shrd') i kasowanie pozostałych 21 bajtów z zerem. Jeśli masz już wartość w rejestrach, sprawdź to pytanie pod kątem wielu zasobów: http://stackoverflow.com/q/25248766/4271923 – Ped7g

+3

@Dalton możesz również użyć AVX2 w trybie 32-bitowym (ograniczone do 8 rejestrów 'ymmN'' choć jak w 'xmmN'). – Ruslan

Odpowiedz

5

W tej odpowiedzi zamierzam mówić tylko o x64.
x86 jest przestarzały od 15 lat, jeśli kodujesz w 2016 r., Nie ma sensu utknąć w 2000 r.
Wszystkie czasy są zgodne z Agner Fog's instruction tables.

Intel Skylake przykładowe czasy *
W shld/shrd instrukcje są raczej powolne na x64.
Nawet na skylake Intela mają opóźnienie 4 cykli i używają 4 upy, co oznacza, że ​​zużywa dużo jednostek wykonawczych, na starszych procesorach jest jeszcze wolniejszy.
zamierzam zakładać chcesz przesunąć o zmiennej wysokości, co oznacza

SHLD RAX,RDX,cl  4 uops, 4 cycle latency. -> 1/16 per bit 

Korzystanie 2 zmiany + dodaj można to zrobić szybciej wolniej.

@Init: 
MOV R15,-1 
SHR R15,cl //mask for later use.  
@Work: 
SHL RAX,cl  3 uops, 2 cycle latency 
ROL RDX,cl  3 uops, 2 cycle latency 
AND RDX,R15  1 uops, 0.25 latency 
OR RAX,RDX  1 uops, 0.25 latency  
//Still needs unrolling to achieve least amount of slowness. 

Należy zauważyć, że przesuwa to tylko 64 bity, ponieważ nie ma wpływu na RDX.
Próbujesz pokonać 4 cykle na 64 bity.

//4*64 bits parallel shift. 
//Shifts in zeros. 
VPSLLVQ YMM2, YMM2, YMM3 1uop, 0.5 cycle latency. 

Jednak jeśli chcesz to zrobić dokładnie to, co robi SHLD musisz używać dodatkowego VPSLRVQ oraz lub, aby połączyć te dwa wyniki.

VPSLLVQ YMM1, YMM2, YMM3 1uop, 0.5 cycle latency. 
VPSRLVQ YMM5, YMM2, YMM4 1uop, 0.5 cycle latency. 
VPOR YMM1, YMM1, YMM5 1uop, 0.33 cycle latency. 

Będziesz musiał przeplatać 4 zestawy tych kosztujących cię (3 * 4) + 2 = 14 YMM rejestrów.
W ten sposób wątpię, że będziesz czerpał korzyści z niskiego opóźnienia VPADDQ wynoszącego 0,33, więc zamiast tego przyjmę 0,5 opóźnienia.
To powoduje, że 3uops, 1,5 cykl opóźnienia dla 256 bitów = 1/171 na bit = 0,37 cyklu na QWord = 10x szybciej, nie jest zły. Jeśli jesteś w stanie uzyskać 1,33 cyklu na 256 bitów = 1/192 na bit = 0,33 cyklu na QWord = 12x szybciej.

'It’s the Memory, Stupid!'
Oczywiście nie dodaliśmy narzut w pętli i obciążenia/sklepy z/do pamięci.
Obwód pętli jest niewielki, biorąc pod uwagę prawidłowe wyrównanie celów skoku, ale dostęp do pamięci będzie z łatwością największym spowolnieniem.
Pojedynczy brak pamięci podręcznej do pamięci głównej w Skylake może kosztować more than 250 cycles1.
To w sprytnym zarządzaniu pamięcią osiąga się duże zyski.
12-krotność przyspieszenia przy użyciu AVX256 to małe ziemniaki w porównaniu.

Nie liczę ustawień licznika zmian w CL/(YMM3/YMM4) ponieważ zakładam, że będziesz używał tej wartości przez wiele iteracji.

Nie zamierzasz tego robić z instrukcjami AVX512, ponieważ procesory klasy konsumenckiej z instrukcjami AVX512 nie są jeszcze dostępne.
Jedynym obecnie obsługiwanym obecnie procesorem jest Knights Landing.

*) Wszystkie te wartości są najlepszymi wartościami i powinny być traktowane jako wskazania, a nie jako wartości twarde.
) Koszt pominięcia pamięci podręcznej w Skylake: 42 cykle + 52ns = 42 + (52 * 4,6 GHz) = 281 cykli.

+1

Po prostu do nitowania, pamięć podręczna chowa się do pamięci na Skylake nie są tak złe, jak 1000 cykli (chyba że liczba błędów strony). To może się zdarzyć tylko wtedy, gdy jest to brak pamięci podręcznej dla bardzo odległego węzła NUMA. Ale to nie jest naprawdę możliwe, ponieważ serwery Skylake z wieloma gniazdami nie zostały jeszcze wydane. – Mysticial

+0

Dzięki, zaktualizowane. – Johan

+0

Huh, to naprawdę dziwne, że w SKL, VPSLLVQ jest bardziej wydajny niż normalny VPSLLQ (który bierze liczbę zmian tylko z dolnego elementu). Wygląda na to, że VPSLLQ SKL używa shuffle port5 do nadawania liczby zmian każdemu elementowi wektora, a następnie przekazuje go do jednostek wykonawczych VPSLLVQ. Na BDW i wcześniejszych, VPSLLQ również pobiera port5 uop, ale VPSLLVQ jest jeszcze wolniejszy. W każdym razie, dla natychmiastowego zliczania zmian (co jest prawdopodobnie powszechne po inliningu), 'VPSLLQ v, v, i' jest zdecydowanie najbardziej efektywnym sposobem. –