Moderni procesory jsou vektorove pocitace; krome klasickych registru maji sadu 128bitovych SSE registru (xmm0-xmm7, na x86_64 i xmm8-xmm15) a nejnovejsi procesory (Sandy Bridge, Bulldozer) je dale rozsiruji na 256bitove AVX registry (ymm0-ymm15). Pri praci s SSE registry je dulezite pouzivat -march=native (nebo jinou rozumnou hodnotu). SSE registry dokaze pouzivat i gcc samo o sobe, bud na nektere floatove vypocty, nebo dokaze i autovektorizovat smycky (nezapomente na -O3) - ovsem tento mechanismus je znacne krehky, gcc musi mit jasno o vlastnostech poctu iteraci cyklu a zaroven samozrejme dokaze vektorizovat pouze nektere operace a stromy operaci. Nastesti se na vektorizator muzeme napichnout pomoci prepinace -ftree-vectorizer-verbose=8 (nebo 16) kdy bude o kazdem cyklu psat informaci, zda jej vektorizoval, pripadne proc ho zvektorizovat nedokazal. Mame-li stesti, muzeme na zaklade techto hintu kod pripadne upravit, aby jej gcc zvektorizovalo samo. Kvalita autovektorizace se s casem vyrazne zlepsuje, rozdil mezi gcc-4.4 (v labu) a gcc-4.6 nebo gcc-4.8 je markantni. Samozrejme ma stale autovektorizator stale limity, na ktere v netrivialnim kodu narazite celkem brzy. V tu chvili je treba zacit pracovat s vektory explicitne. To lze delat primo rozsirenim gcc: unsigned int x __attribute__((vector_size(16))); unsigned int y __attribute__((vector_size(16))); return x + y * 2; Timto jsme si nadeklarovali dve vektorove promenne x, y. Zpusob je trochu prapodivny, rikame, ze x, y jsou 16-bytove promenne plne unsigned intu. SSE registry budou 16-bytove, AVX registry 32-bytove (to uz je polovina cacheline!). S temito typi gcc umi vetsinu prirozenych aritmetickych operaci, prirazovani atd. Radu instrukci ale neumi, napr. bitove shifty. To je chvile pro intrinsics. Intrinsics jsme potkali uz minule; jedna se o Intelem vymysleny zpusob, jak mit v C k disposici ruzna exoticka x86 rozsireni; funguje i v icc, MSVC++ atd. Includujme si: #include Ten zavadi typ __m128i (aj. pro floaty a double, MMX a AVX atd.). Intrinsics se pak pouzivaji stylem __m128i x, y; x = _mm_set_epi32(1, 2, 3, 4); y = _mm_setzero_si128(); x |= _mm_srli_epi32(y, 8); Protoze __m128i je interne zatypedefovany jako gcc vektor, muzeme opet pouzivat = a dalsi zakladni operace (i kdyz to neni moc portabilni). Suffix "epi32" znamena 32-bitove inty v "extended packed" formatu (proste ulozene v SSE registru jako vektor; muzeme operovat i se "scalar" formatem, kdy pracujeme pouze s nejnizsi hodnotou ve vektoru, a s "packed" formatem, ktery pouziva blahe pameti MMX registry). Sifra srli znamena "Shift Right Logical Immediate" - shiftovat muzeme i aritmeticky (rozmazava znamenko) a misto immediate hodnoty muze velikost posunu udavat hodnota z nejakeho registru. Dokumentaci naleznete treba v Intelich manualech odkazovanych ze stranky cviceni, pomuze vam i Googleni (online dokumentace k MSVC++ celkem pekne intrinsics pokryva). (i) Spoctete Hammingovu vzdalenost mezi dvemi bitmapovymi obrazky. Nejdriv si rozmyslete, co je Hammingova vzdalenost a jak ji obecne pocitat. aim-kit je k tomu treba ponekud drasticteji priohnout, muzete zacit s hamming.c ze stranky cviceni - zkuste ho zkompilovat, overit funkcnost (noise obrazky jsou od standardnich testovacich daleko 10000 pixelu), zmerit rychlost, pochopit kod atd. (ii) Overte, zda gcc puvodni hamming.c zautovektorizuje. Proc a jak to pripadne opravit? (iii) Upravte priklad tak, aby pracoval se slovy (inty) misto po bajtech. (iv) A co ted, pomohlo to autovektorizaci? Pokud ano, sla by jeste nejak vylepsit? (Odpoved zavisi na verzi gcc.) (v) Pomohlo by pouzit __builtin_popcount? (Odpoved neni primocara a zase zavisi na verzi gcc, jestli vas procesor umi SSE4.2 atd.) (vi) Zkuste cyklus zvektorizovat explicitne pomoci intrinsics. ---- Hinty a reseni (i) Hammingova vzdalenost odpovida poctu rozdilnych bitu v obrazku. Tedy poctu jednickovych bytu (population count) v xoru bitmap. (ii) Nezautovektorizuje - v asm. kodu funkce exercise() neni po SSE instrukcich ani stopy. -ftree-vectorizer-verbose=8 vysvetli, ze gcc neumi zautovektorizovat operaci <<, protoze operandem je byte. Pokud bychom ji opravili tak, aby operandem byl int, razem bude situace jina, staci tedy upravit funkci popc(). (iii) http://pasky.or.cz/vyuka/2012-DMI074/hamming-int.c (iv) Ano. S gcc-4.4 a starsimi vsak kod neni kod zdaleka optimalni, vyuziva se totiz pouze polovina SSE registru; mohli bychom pocitat dva popcounty paralelne. Modernejsi gcc uz cyklus asi prelozi lepe... (v) Potiz je v tom, ze starsi verze gcc neumeji autovektorizovat __builtin_popcount(). Minimalne gcc-4.8 uz umi. (vi) http://pasky.or.cz/vyuka/2012-DMI074/hamming-sse.c