400 likes | 617 Views
SIMD. Single Instruction Multiple Data. Piotr Dałek. Motywacja. Złożone przetwarzanie dużej ilości danych Dużo kodu Głodzone segmenty procesorów Coraz większe wymagania względem jakości treści Marketing i wojny rynkowe. Dużo kodu. Pascal:. Dużo kodu.
E N D
SIMD Single InstructionMultiple Data Piotr Dałek
Motywacja • Złożone przetwarzanie dużej ilości danych • Dużo kodu • Głodzone segmenty procesorów • Coraz większe wymagania względem jakości treści • Marketing i wojny rynkowe
Dużo kodu • Pascal:
Dużo kodu ainUnit.pas.152: destvtx.x := ((srcvtx.x*(transmat^)[0]) + (srcvtx.y * (transmat^)[1]) + (srcvtx.z * (transmat^)[2])); 00453A2E D906 flddwordptr [esi] 00453A30 D808 fmuldwordptr [eax] 00453A32 D94604 flddwordptr [esi+$04] 00453A35 D84804 fmuldwordptr [eax+$04] 00453A38 DEC1 faddpst(1) 00453A3A D94608 flddwordptr [esi+$08] 00453A3D D84808 fmuldwordptr [eax+$08] 00453A40 DEC1 faddpst(1) 00453A42 D91B fstpdwordptr [ebx] 00453A44 9B wait MainUnit.pas.153: destvtx.y := ((srcvtx.x*transmat^[4]) + (srcvtx.y * transmat^[5]) + (srcvtx.z * transmat^[6])); 00453A45 D906 flddwordptr [esi] 00453A47 D84810 fmuldwordptr [eax+$10] 00453A4A D94604 flddwordptr [esi+$04] 00453A4D D84814 fmuldwordptr [eax+$14] 00453A50 DEC1 faddpst(1) 00453A52 D94608 flddwordptr [esi+$08] 00453A55 D84818 fmuldwordptr [eax+$18] 00453A58 DEC1 faddpst(1) 00453A5A D95B04 fstpdwordptr [ebx+$04] 00453A5D 9B wait MainUnit.pas.154: destvtx.z := ((srcvtx.x*transmat^[8]) + (srcvtx.y * transmat^[9]) + (srcvtx.z * transmat^[10])) + 400; 00453A5E D906 flddwordptr [esi] 00453A60 D84820 fmuldwordptr [eax+$20] 00453A63 D94604 flddwordptr [esi+$04] 00453A66 D84824 fmuldwordptr [eax+$24] 00453A69 DEC1 faddpst(1) 00453A6B D94608 flddwordptr [esi+$08] 00453A6E D84828 fmuldwordptr [eax+$28] 00453A71 DEC1 faddpst(1) 00453A73 D805043B4500 fadddwordptr [$00453b04] 00453A79 D95B08 fstpdwordptr [ebx+$08] 00453A7C 9B wait […] Zaledwie fragment!
Dużo kodu movups xmm0, [ecx] movups xmm1, [ecx+$10] movups xmm2, [ecx+$20] @next: movups xmm4, [eax+esi] movups xmm5, xmm4 movups xmm6, xmm4 mulps xmm4, xmm0 movups [edx+esi], xmm4 mulps xmm5, xmm1 movups [edx+esi+$10], xmm5 fld [edx+esi] fadd [edx+esi+4] fadd [edx+esi+8] mulps xmm6, xmm2 movups [edx+esi+$20], xmm6 fstp [edx+esi] fld [edx+esi+16] fadd [edx+esi+20] fadd [edx+esi+24] fstp [edx+esi+4] fld [edx+esi+32] fadd [edx+esi+36] fadd [edx+esi+40] fadd [eax+esi+12] // srcvrt.zero fst [edx+esi+8] fldxe fdivst(0), st(1) fldst(0) fmul [edx+esi] fstp [edx+esi] movups xmm4, [edx+esi] mulps xmm4, xmm4 movups [edi], xmm4 fld [edi] fadd [edi+4] fadd [edi+8] fstp [edi] addedi, 4 addesi, $10 jnz @next pop edi movesi, 196608*4 moveax, maxmem addeax, esi negesi movups xmm0, [eax+esi] sqrtps xmm1, xmm0 movups xmm1, [eax+esi] sqrtps xmm0, xmm1 @nextmax: movups xmm2, [eax+esi] sqrtps xmm2,xmm2 maxps xmm0, xmm2 movups [eax+esi], xmm2 minps xmm1, xmm2 addesi, 16 jnz @nextmax moveax, maxmem movups [eax], xmm0 movups [eax+16], xmm1 Całość!
Głodzone segmenty procesora • XOR jako instrukcja prosta SIMD • Potoki U oraz V • Niewykorzystana przepustowość szyny • Hyperthreading jako automat do zwiększania utylizacji segmentów procesora
XOR - SIMD • Performs a bitwise exclusive OR (XOR) operation on the destination (first) and source (second) operands andstores the result in the destination operand location. The source operand can be an immediate, a register, or amemory location; the destination operand can be a register or a memory location. (However, two memory operandscannot be used in one instruction.) Each bit of the result is 1 if the corresponding bits of the operands aredifferent; each bit is 0 if the corresponding bits are the same. • This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
XOR - SIMD • XOR EAX, EBX A B C D EAX XOR W X Y Z EBX A^W B^X C^Y D^Z EAX
XOR - SIMD • Szyfrowanie 8-miobitowym kluczem • Szyfrowanie po 8, 16 i 32 bity naraz • Pętla warunkowa bez warunku • Base+Displacement
XOR - SIMD • Zawodnik #1: Pentium I (200MHz) • 8KB L1 Cache • 8bit – ZZZzzZZZzzZzzzzzzzzzzz… …477,40s • 16bit – 310,35s = 1,53x • 32bit – 268, 4 s = 1,77x • 32bit_4 – 268,4 s = 1,77x
XOR - SIMD • Zawodnik #2: Atom 330 (1,6GHz, HT) • 24kB L1 Cache (x2, per rdzeń) • 8bit – 101,6 s = 1x • 16bit – 47,2 s = ~2,15x • 32bit – 17,38 s = ~5,84x • 32bit_4 – 16,97 = ~5,98x
XOR - SIMD • Zawodnik #3 - Core 2 Duo E4400 (2GHz + ovrclk 0,4GHz) • 64kB L1 cache (per rdzeń, 32kB instrukcje, 32kB dane) • 8bit – 10,1 s = 1x • 16bit – 7,75 s = ~1,30x • 32bit – 7,50 s = ~1,34x • 32bit_4 – 7,30s = ~1,38x
XOR - SIMD • Zawodnik #4 - Corei7 2660 (3,4GHz) • 64kB L1 cache (per rdzeń) • 8bit – 9,44 s = 1x • 16bit – 4,90 s = ~1,92x • 32bit – 2,65 s = ~3,56x • 32bit_4 – 2,11 s = ~4,47x
XOR - SIMD • Pamięć wąskim gardłem? • 16MB*1000/7,30 = 2191 MB/s = >2GB/s • E4400 ma zaledwie 2 MB cache l2, wykluczamy „palenie cache” • DDR2/800 ma teoretyczną przepustowość 6,4GB/s • Dlaczego takie małe różnice dla C2d i takie gigantyczne dla Atoma330 i Core i7?
Potoki U i V • Pentium procesorem superskalarnym • V na instrukcje proste • U na wszystkie instrukcje • Instrukcje w potoku U wykonują się równolegle z instrukcjami z potoku V • Out-of-orderexecution automatyzuje to
Niewykorzystana przepustowość • 2,1GB < 6,4GB • FPU jako akcelerator kopiowania pamięci • 8 bajtów > 4 bajty • Dobre rozwiązanie nie zawsze brzmi rozsądnie ;) @@Loop: fildqwordptr [eax+ecx] fistpqwordptr [edx+ecx] addecx, 8 jl @@Loop
Rosnące wymagania • Doom: 320x200x8bpp
Rosnące wymagania • GLQuake: WOOO SVGA!!!1111oneoneoneeleven • (640x480x16bpp)
Rosnące wymagania • SupremeCommander - dwa monitory (1680x1050 + 1024x768), pełne 3D
MMX • MultiMediaeXtensions • 1997 rok • Nikt nie był na to przygotowany, WIEEELKI zawód ze strony Wintela • EMMS
MMX na przykładzie PADDB A B C D E F G H MM0 PACKED ADD BYTES S T U V W X Y Z MM1 A+S B+T C+U D+V E+W F+X G+Y H+Z MM0
MMX na przykładzie PADDB • Jedna instrukcja równoważna ośmioprzebiegowej pętli albo 16 instrukcjom • Problem – ilości danych nie wyrównane do paczek MMX
MMX – kolejny przykład • Unicode • Dwa bajty na znak • ASCII to podzbiór Unicode • Kompresja 50% bez strat! • Przepleść znaki zerami • Wersja MMX 4x szybsza niż x86 • Oszczędność miejsca bez wyraźnej straty wydajności Wikipedia - weryfikacja adresu e-mail P.i.o.t.r. .D.a.B☺e.k.
MMX – kolejny przykład • pxor mm0, mm0 • @next: • movq mm1, [eax ] • movq mm2, [eax+8] • addeax, 16 • packuswb mm1, mm2 • movq [edx], mm1 • addedx, 8 • cmpeax, ecx • jne @next • emms • @nextslow: • pop ecx • cmpecx, eax • jbe @sret • @nextsl: • movbl, [eax] • mov [edx], bl • addeax, 2 • addedx, 1 • cmpecx, eax • jne @nextsl • @sret: • pop ebx • @qret: pxor mm0, mm0 @next: movd mm1, [eax] movd mm2, [eax+4] punpcklbw mm1, mm0 punpcklbw mm2, mm0 movq [edx ], mm1 movq [edx+8 ], mm2 addeax, 8 addedx, 16 cmpeax, ecx jb @next emms
3DNow • MMX nie zapewniało instrukcji obsługujących wektory zmiennoprzecinkowe • Pole do popisu dla AMD, próba monopolizowania rynku • Intel oczywiście tego nie wspiera… • Niedawno się z tego całkowicie wycofali, więc odłóżmy to do lamusa • Perełki: FEMMS, PREFETCH/PREFETCHW, operacje horyzontalne (Intel śpi aż do SSE3)
SSE • Streaming SIMD Extensions • Odpowiedź Intela na 3DNow • Szereg nowych instrukcji, nowe rejestry (XMMx) • MOVUPS, MOVAPS, i „nie rozumiem czemu *TU* mam GPFy!” • Przykładowy program
SSE2 • 128-bitowe typy danych • DUŻO instrukcji do zabawy z wektorami zmiennoprzecinkowymi • Instrukcje do hurtowej konwersji Single/Double/Int • Kontrola nad Cache • CFLUSH • MOVNTI, MOVNTDQ, MOVNTPD • Pause
SSE3 • Działanie w poziomie • HSUBPS, HADDPS, HADDPD, HSUBPD • MONITOR, MWAIT • Zwiększenie wydajności przy dostępie na skraju linii pamięci cache • LDDQU • Duplikacja elementów • MOVSHDUP, MOVSLDUP, MOVDDUP
AES-NI • Intel pozazdrościł VIA ichniego PadlockEngine • Nowe instrukcje zapewniające sprzętowe szyfrowanie AES: • AESDEC, AESDECLAST, AESENC, AESENCLAST, AESIMC, AESKEYGENASSIST.
AVX • AdvancedVectorExtensions • 256-bitowe typy danych • Wyższa szkoła jazdy ;)
Problemy z SIMD • Niebanalne • Optymalny układ danych nie jest optymalny w rozumieniu X0 Y0 Z0 00 X1 Y1 Z1 00 X0 X1 X2 X3 Y0 Y1 Y2 Y3
Problemy z SIMD • Wymagane dokładne rozumienie, co i jak działa • Trik z pętlą bez cmp to chleb powszedni • Wymaga dużo czasu programisty • Różne wsparcie dla różnych instrukcji i konieczność radzenia sobie z różnymi konfiguracjami • Mam SSE, ale nie mam HADDPS, bo to jest dopiero w SSE3 • Używam recznej serii FADD(P) albo tak organizuję dane, bym mógł użyć ADDPS
Przykładowa aplikacja • „Obraz na powierzchni” • Wcale nie texture-mapping
Przykładowa aplikacja • Delphi 7, natywny kod • Wersja z „przelicznikiem” działającym po FPU • Wersja SSE • Wersja CUDA (w trakcie) • C#, p-c0de • ASM niedopuszczalny (zasadniczo) • Wskaźniki są be • Nie oczekujmy cudów
Konkluzje • Kod SSE słabo zoptymalizowany • Użycie HADDPS albo reorganizacja elementów wektorów zapewne poprawiłaby wydajność • Wynik, jak na pierwszy raz, zadowalający • Duży potencjał leżący w instrukcjach SSE który nie jest łatwy w wykorzystaniu • Zabawa dla kombinatorów
Wsparcie mechaniczno-merytoryczne • Intrinsice • Wszędzie • Automatyczna wektoryzacja (GCC, ICC, MSVC?) • ICC drogi i wredny • Udowodniono, że wygenerowany kod „degraduje” się na procesorach AMD • vTune i inne wynalazki • Dokumentacja Intela