Jakie są najlepsze sekwencje instrukcji do generowania stałych wektorowych w locie?

"najlepsza" oznacza najmniejszą liczbę instrukcji (lub najmniejszą liczbę uops, jeśli jakakolwiek Instrukcja dekoduje się do więcej niż jednego uop). Rozmiar kodu maszynowego w bajtach jest tie-breaker dla równej liczby insn.

Stałe generowanie jest ze swej natury początkiem nowego łańcucha zależności, więc opóźnienie jest nietypowe dla materii. Nietypowe jest również generowanie stałych wewnątrz pętli, więc wymagania dotyczące przepustowości i portu wykonania również są w większości nieistotne.

Generowanie stałych zamiast ich ładowania zajmuje więcej instrukcji (z wyjątkiem all-zero lub all-one), więc zużywa cenną przestrzeń uop-cache. Może to być jeszcze bardziej ograniczony zasób niż pamięć podręczna danych.

Doskonała instrukcja optymalizacji montażu Agner Fog obejmuje to w Section 13.4. Tabela 13.10 zawiera sekwencje do generowania wektorów, gdzie każdy element jest 0, 1, 2, 3, 4, -1, lub -2, o rozmiarach elementów od 8 do 64 bitów. Tabela 13.11 zawiera sekwencje do generowania niektórych wartości zmiennoprzecinkowych (0.0, 0.5, 1.0, 1.5, 2.0, -2.0, i bitmaski na znak bit.)

Sekwencje Agnera Foga używają tylko SSE2, z założenia lub dlatego, że nie były aktualizowane przez jakiś czas.

Jakie inne stałe można wygenerować za pomocą krótkich nieoczywistych sekwencji instrukcji? (dalsze rozszerzenia o różnych liczbach przesunięć są oczywiste i nie "interesujące".) Czy są lepsze sekwencje do generowania stałych?

Jak przenieść 128-bit bezpośrednio do rejestrów XMM ilustruje kilka sposobów umieszczenia dowolnej stałej 128b w strumieniu instrukcji, ale to zwykle nie jest sensowne (nie oszczędza miejsca i zajmuje dużo miejsca w pamięci podręcznej uop.)

Author: Community, 2016-01-29

1 answers

Wszystkie-zero: pxor xmm0,xmm0 (LUB xorps xmm0,xmm0, o jeden bajt instrukcji krótszy.)

All-one: pcmpeqw xmm0,xmm0. Jest to zwykły punkt wyjścia do generowania innych stałych, ponieważ (jak pxor) zrywa zależność od poprzedniej wartości rejestru (z wyjątkiem starych procesorów, takich jak K10 i pre-Core2 P6). Wersja W nie ma przewagi nad wersjami pcmpeq o rozmiarze bajtów lub dword na dowolnym procesorze w tablicach instrukcji Agner Fog, ale pcmpeqQ zajmuje dodatkowy bajt, jest wolniejsza na Silvermont i wymaga SSE4. 1.

Więc nie ma tak naprawdę formatowania tabel , więc po prostu zamierzam wymienić dodatki do tabeli Agner Fog 13.10, a nie ulepszoną wersję. Przepraszam. Może jeśli ta odpowiedź stanie się popularna, użyję generatora tabel ascii-art, ale mam nadzieję, że ulepszenia zostaną wprowadzone do przyszłych wersji przewodnika.


Główną trudnością jest 8Bit wektorów, ponieważ nie ma PSLLB

Tablica Agnera Foga generuje wektory 16bitowe elements and uses packuswb to work around this. Na przykład, pcmpeqw xmm0,xmm0 / psrlw xmm0,15 / psllw xmm0,1 / packuswb xmm0,xmm0 generuje wektor, w którym każdy bajt jest 2. (Ten wzór przesunięć, o różnych liczbach, jest głównym sposobem na uzyskanie większości stałych dla szerszych wektorów). Jest lepszy sposób:

paddb xmm0,xmm0 (SSE2) działa jako przesunięcie w lewo o jeden z bajtami ziarnistości, więc wektor -2 bajtów można wygenerować tylko za pomocą dwóch instrukcji (pcmpeqw / paddb). paddw/d/q jako przesunięcie w lewo po jednym dla innych rozmiary elementów zapisują jeden bajt kodu maszynowego w porównaniu do zmian i zazwyczaj mogą działać na większej liczbie portów niż shift-imm.

pabsb xmm0,xmm0 (SSSE3) zamienia wektor wszystkich jedynek (-1) w wektor 1 bajtów , tak że zajmuje tylko dwie instrukcje. Możemy wygenerować 2 bajtów z pcmpeqw / paddb / pabsb. (Kolejność dodawania vs. abs nie ma znaczenia). pabs nie wymaga imm8, ale tylko zapisuje bajty kodu dla innych szerokości elementów w porównaniu z przesunięciem w prawo, gdy oba wymagają 3-bajtowy prefiks VEX. Dzieje się tak tylko wtedy, gdy rejestr źródłowy to xmm8-15. (vpabsb/w/d zawsze wymaga 3-bajtowego prefiksu VEX dla VEX.128.66.0F38.WIG, ale vpsrlw dest,src,imm może w przeciwnym razie użyć 2-bajtowego prefiksu VEX dla VEX.NDD.128.66.0F.WIG).

Możemy zapisywać instrukcje w generowaniu 4 bajty Zobacz też: pcmpeqw / pabsb / psllw xmm0, 2. Wszystkie bity, które są przesunięte przez granice bajtów przez słowo-shift, są zerowe, dzięki pabsb. Oczywiście inne liczenia przesunięć mogą umieścić pojedynczy set-bit w innych miejscach, w tym bit znaku generuje wektor -128 (0x80) bajtów. Zauważ, że pabsb jest nieniszczący(docelowy operand jest tylko do zapisu i nie musi być taki sam jak Źródło, aby uzyskać pożądane zachowanie). Można zachować wszystkie-jako stałą, lub jako początek generowania innej stałej, lub jako operand źródłowy dla psubb (aby zwiększyć o jeden).

A wektor 0x80 bajtów może być również (Patrz poprzedni akapit) generowany z wszystko, co nasyca się do -128, używając packsswb. na przykład jeśli masz już wektor 0xFF00 dla czegoś innego, po prostu skopiuj go i użyj packsswb. Stałe ładowane z pamięci, które prawidłowo się nasycają, są potencjalnymi celami tego celu.

A wektor 0x7f bajtów można wygenerować z pcmpeqw / psrlw xmm0, 9 / packuswb xmm0,xmm0. Liczę to jako "nieoczywiste", ponieważ głównie ustawiona natura nie skłoniła mnie do myślenia o generowaniu go jako wartości w każdym słowie i robieniu zwykłych packuswb.

pavgb (SSE2) w stosunku do zerowanego rejestru może przesunąć się o jeden, ale tylko wtedy, gdy wartość jest parzysta. (Robi unsigned dst = (dst+src+1)>>1 dla zaokrąglania, z 9-bitową wewnętrzną precyzją dla tymczasowego.) Nie wydaje się to jednak przydatne w przypadku ciągłego generowania, ponieważ 0xFF jest dziwne: pxor xmm1,xmm1 / pcmpeqw xmm0,xmm0 / paddb xmm0,xmm0 / pavgb xmm0, xmm1 produkuje 0x7f bajtów o jeden insn więcej niż shift / pack. Jeśli wyzerowany rejestr jest już potrzebny do czegoś innego, choć, paddb / pavgb does save one bajt instrukcji.


Przetestowałem te sekwencje. Najprostszym sposobem jest wrzucenie ich do .asm, złożenie / link i uruchomienie na nim gdb. layout asm, display /x $xmm0.v16_int8 aby zrzucić to po każdym kroku i instrukcjach jednostopniowych (ni lub si). W trybie layout reg, możesz zrobić tui reg vec, aby przełączyć się na wyświetlanie regów wektorowych, ale jest to prawie bezużyteczne, ponieważ nie możesz wybrać interpretacji do wyświetlenia (zawsze masz je wszystkie i nie możesz hscroll, a kolumny nie ustawiają się między rejestry). Jest to jednak doskonałe dla regs/FLAG integer.

Zauważ, że używanie ich z wewnętrznymi elementami może być trudne. Kompilatory nie lubią operować na niezainicjowanych zmiennych, dlatego należy używać _mm_undefined_si128() powiedzieć kompilatorowi, że o to ci chodziło. A może użycie {[64] } sprawi, że Twój kompilator będzie emitował pcmpeqd same,same. Bez tego niektóre Kompilatory przed użyciem będą xor-zero niezinicjalizowanych zmiennych wektorowych, a nawet (MSVC) ładują niezinicjalizowaną pamięć z stack.


Wiele stałych może być przechowywanych w pamięci bardziej kompaktowo, korzystając z SSE4.1 pmovzx lub pmovsx dla zera lub rozszerzenia znaku w locie. Na przykład wektor 128b zawierający {1, 2, 3, 4} jako 32-bitowe elementy może być generowany z ładowaniem pmovzx Z 32-bitowego miejsca pamięci. Operandy pamięci mogą mikro-fuse z pmovzx, więc nie wymaga to żadnych dodatkowych UOPs z topioną domeną. Zapobiega to jednak używaniu stałej bezpośrednio jako operandu pamięci.

C/C++ wsparcie dla używania pmovz/sx jako obciążenia jest straszne : istnieje _mm_cvtepu8_epi32 (__m128i a), ale żadna wersja, która wymaga operanda wskaźnika uint32_t *. Można się włamać, ale jest brzydki i błąd optymalizacji kompilatora jest problemem. Zobacz połączone pytanie po szczegóły i linki do raportów o błędach gcc.

Z stałymi 256b i (nie tak) wkrótce 512B, oszczędności w pamięci są większe. Ma to duże znaczenie tylko wtedy, gdy wiele użytecznych stałych może współdzielić linię bufora.

Odpowiednik FP jest to VCVTPH2PS xmm1, xmm2/m64, wymagające flagi funkcji F16C (half precision). (Istnieje również instrukcja store, która pakuje pojedynczo do połowy, ale bez obliczeń z połową precyzji. Jest to tylko optymalizacja przepustowości pamięci / pamięci podręcznej.)


Oczywiście, gdy wszystkie elementy są takie same (ale nie nadają się do generowania w locie), pshufd lub AVX vbroadcastps / AVX2 vpbroadcastb/w/d/q/i128 są przydatne. pshufd może pobierać operand źródłowy pamięci, ale musi to być 128B. movddup (SSE3) wykonuje 64bitowe obciążenie, transmituje do wypełnij rejestr 128b. Na Intelu nie potrzebuje jednostki wykonawczej ALU, tylko Port load. (Podobnie, AVX v[p]broadcast ładunki o rozmiarze dword i większe są obsługiwane w jednostce ładunkowej, bez ALU).

Transmisje lub {[71] } są doskonałe do zapisywania rozmiaru pliku wykonywalnego, gdy zamierzasz załadować maskę do rejestru w celu wielokrotnego użycia w pętli. Generowanie wielu podobnych masek z jednego punktu początkowego może również zaoszczędzić miejsce, jeśli zajmuje tylko jedną instrukcję.

Zobacz dla wektora SSE, który ma wszystkie te same składniki, generować w locie lub przed komputerem?, który pyta więcej o użycie set1 wewnętrznej, i nie jest jasne, czy pyta o stałe lub transmisje zmiennych.

Eksperymentowałem również zkompilatorem dla transmisji .


Jeśli błędy pamięci podręcznej są problemem , spójrz na swój kod i sprawdź, czy kompilator ma zduplikowane stałe _mm_set, gdy ta sama funkcja jest inline w różnych rozmówców. Zwróć również uwagę na stałe używane razem (np. w funkcjach nazywanych jedna po drugiej), które są rozrzucane do różnych linii pamięci podręcznej. Wiele rozproszonych ładunków dla stałych jest o wiele gorsze niż ładowanie wielu stałych z blisko siebie.

pmovzx i / lub obciążenia transmisji pozwalają spakować więcej stałych do linii pamięci podręcznej, z bardzo niskim obciążeniem na załadowanie ich do rejestru. Obciążenie nie będzie na ścieżce krytycznej, więc nawet jeśli wymaga dodatkowego uop, może przyjmować wolną jednostkę wykonawczą w dowolnym cyklu przez długie okno.

Clang faktycznie wykonuje dobrą robotę : oddzielne stałe set1 w różnych funkcjach są rozpoznawane jako identyczne, tak jak identyczne literały łańcuchowe mogą być scalane. Zauważ, że wyjście źródłowe ASM clanga wydaje się pokazywać, że każda funkcja ma własną kopię stałej, ale demontaż binarny pokazuje, że wszystkie te efektywne adresy względne do RIP odnoszą się do tej samej lokalizacji. Dla 256b clang używa również vbroadcastsd, aby wymagać tylko obciążenia 8B, kosztem dodatkowej instrukcji w każdej funkcji. (Jest to at -O3, więc najwyraźniej twórcy clang zdali sobie sprawę, że rozmiar ma znaczenie dla wydajności, a nie tylko dla -Os). IDK dlaczego nie schodzi do stałej 4B z vbroadcastss, bo to powinno być tak samo szybkie. Niestety, vbroadcast nie pochodzi po prostu z części stałej 16B innych używanych funkcji. Może to ma sens: AVX wersja czegoś mogłaby prawdopodobnie połączyć tylko niektóre jego stałe z wersją SSE. Lepiej zostawić strony pamięci ze stałymi SSE całkowicie zimne, a wersja AVX zachować wszystkie swoje stałe razem. Jest to również trudniejszy problem z dopasowaniem wzorca do obsługi w czasie montażu lub połączenia (Jednak jest to zrobione. Nie czytałem każdej dyrektywy, żeby dowiedzieć się, która umożliwia połączenie.)

Gcc 5.3 również Scala stałe, ale nie używa broadcast-loads do Kompresuj stałe 32B. Znowu stała 16B nie pokrywa się ze stałą 32B.

 20
Author: Peter Cordes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:13