Tyjo to uz zacina byt pekne komplikovane. Fakt to umi nektery prekladace pouzivat s beznym imperativnim kodem plnym ruznych loopu? Nebo uz je to vec pro par specialistu a knihoven?
* GCC to imho moc nezvlada, clang jsem slysel, ze je lepsi, cekal bych, ze nejlepsi bude IC, ale ten tedy kupovat nebudeme :)
> GCC to imho moc nezvlada, clang jsem slysel, ze je lepsi, cekal bych, ze nejlepsi bude IC, ale ten tedy kupovat nebudeme :)
Jak v čem. Občas vidím kód, který GCC zvládne líp, občas je to Clang. Ten základ zvládají asi oba už podobně (i když IMHO třeba AVX-512 automaticky nevyužívají), ty větší speciality ale závisí od toho, co už bylo implementováno - jinými slovy - co si tvůrci myslí, že by mělo přinést reálný užitek v běžném kódu.
Podle mě to autor moc komplikuje. AVX-512 jsou v podstatě 2 varianty, baseline a icelake (Zen4 je v podstatě icelake). Intel jen začal používat pro každou rodinu instrukcí vlastní CPUID bit, takže to celkově vypadá jako že těch rozšíření je hodně, v praxi to ale tak není. Nikdy nevyjde CPU, který by neměl třeba AVX512-BW, ale měl by AVX512-DQ, atd...
Baseline je F, CD, DQ, BW, VL - toto je opravdu základ pro jakýkoliv X86 procesor co podporuje AVX-512. Ono třeba na wiki je to hezky v tabulce:
https://en.wikipedia.org/wiki/AVX-512
Knights Landing a Knights Mill je Xeon Phi akcelerátor, o tom se nemá cenu bavit, protože ten je dávno mrtvý a jedná se o jiné kódování instrukcí (není to klasický X86 CPU).
Takže ano, baseline je Skylake-SP a Skylake-X, a potom máme Ice Lake a Zen4, což bych označil za "AVX-512 level 2", protože tam je hodně malý rozdíl. To co je mezi tím nemá cenu řešit - třeba VP2INTERSECT u Tiger Lake - nikdo to nepoužívá...
Z pohledu někoho kdo AVX-512 používá bych řekl, že z těch dalších rozšíření je nejužitečnější AVX512_VBMI a AVX512_VBMI2 - VPOPCNTDQ se dá obejít (je to pár instrukcí) a ten zbytek je opět celkem specifický, nehodí se pro běžné věci.
Pomocí instrukcí AVX2 jsem naprogramoval funkci pro šifrování HMAC, SHA1. Funkci SHA1 mi funguje pro vstupní text "Ahoj1234", který je menší než 64 bytů. Po doplnění funkce HMAC mi ale výpočet s testovacími daty nefunguje správně a nemohu přijít na to kde mám chybu.
Testovací data pro HMAC, SHA1 jsou:
key = "Jefe"
message = "what do ya want for nothing?"
viz Test Cases for HMAC-MD5 and HMAC-SHA-1
Výsledek by měl být:
effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
spočítá ale:
974020ac606d38554f91a4786d23aec3cf4d77de
viz HMAC generator zde
SHA1 generator tady
Vlastní kód (Visual Studio 2022, C++ a AVX2)
32 bitová data ukládá Visual Studio 2022 ve tvaru little endian tj. string "Ahoj1234" je uložen jako "johA" a "4321" (lsb na první místo v paměti).
Specifikace HMAC požaduje uložení počtu bitů šifrované zprávy jako 64bitové big-endian číslo, pracuji s ním ale jako s 32 bitovým little-endian číslem (posledních 32 bitů v druhé AVX2 části 128 bitů a vyzkoušel jsem, že pro test SHA1 "Ahoj1234" to funguje správně.
#include <iostream> #include <immintrin.h> #include <chrono> #include <stdint.h> #include <stdio.h> #include <intrin.h> // Byte swap, unsigned long _byteswap_ulong(unsigned long value); #ifndef __cplusplus #include <stdalign.h> // C11 defines _Alignas(). This header defines alignas() #endif using namespace std; using namespace std::chrono; uint32_t result; __m256i sha1res; // výsledek SHA1 __m256i key; __m256i indata[4]; //2x 512 bitů pro vstup HMAC // HMAC block size block size of the SHA1 hash function is 64 bytes, output size pro SHA1 is 20 bytes // https://en.m.wikipedia.org/wiki/HMAC // SHA1 // https://en.wikipedia.org/wiki/SHA-1 void p256_hex_u32(__m256i in) { alignas(32) uint32_t v[8]; _mm256_maskstore_epi32((int*)v, _mm256_setr_epi32(-1, -1, -1, -1, -1, 0, 0, 0), in); printf("v8_u32: %x %x %x %x %x\n", v[0], v[1], v[2], v[3], v[4]); } inline uint32_t rotl32_30(uint32_t value) { return value << 30 | value >> 2; } inline uint32_t rotl32_5(uint32_t value) { return value << 5 | value >> 27; } inline uint32_t rotl32_1(uint32_t value) { return value << 1 | value >> 31; } uint32_t rotl32(uint32_t value, unsigned int count) { return value << count | value >> (32 - count); } void SHA1(__m256i* indata) { uint32_t pole[80]; __m256i data; uint32_t h0 = 0x67452301, a = h0; uint32_t h1 = 0xEFCDAB89, b = h1; uint32_t h2 = 0x98BADCFE, c = h2; uint32_t h3 = 0x10325476, d = h3; uint32_t h4 = 0xC3D2E1F0, e = h4; for (int k = 0; k < 2; k++) { data = _mm256_load_si256(indata + k * 2); _mm256_store_si256((__m256i*)pole, data); data = _mm256_load_si256(indata + k * 2 + 1); _mm256_store_si256((__m256i*)pole + 1, data ); uint32_t temp; for (int i = 0; i < 80; i++) { if (i > 15) { pole[i] = rotl32_1((pole[i - 3] ^ pole[i - 8] ^ pole[i - 14] ^ pole[i - 16])); } temp = rotl32_5(a) + e + pole[i]; if (i < 20) { temp += ((b & c) | ((~b) & d)) + 0x5A827999; } else if (i < 40) { temp += (b ^ c ^ d) + 0x6ED9EBA1; } else if (i < 60) { temp += ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC; } else { temp += (b ^ c ^ d) + 0xCA62C1D6; } e = d; d = c; c = rotl32_30(b); b = a; a = temp; } h0 += a; h1 += b; h2 += c; h3 += d; h4 += e; } sha1res = _mm256_setr_epi32(h0, h1, h2, h3, h4, 0, 0, 0); } int main() { __m256i tmp; auto start = high_resolution_clock::now(); //Pro test funkce SHA1 lze odkomentovat (a komentovat přiřazení pro Jefe..., nutno také změnit parametr pro načítání k, místo k < 2 na k < 1 a dát stopku za sha1res = ... //sha1res = 8162b79671480fc441e2d54ee85dea77f43b5bc2 //viz odkaz na generátor SHA1 v úvodu //key = _mm256_setr_epi32(0x41686f6a, 0x31323334, 0x80000000, 0, 0, 0, 0, 0); // "Ahoj1234" //indata[0] = key; //indata[1] = _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, 0x40); //key = _mm256_setr_epi32(_byteswap_ulong(0x4A656665), 0, 0, 0, 0, 0, 0, 0); // Jefe key = _mm256_setr_epi32(0x4A656665, 0, 0, 0, 0, 0, 0, 0); // Jefe indata[0] = _mm256_xor_si256(key, _mm256_set1_epi8(0x36)); indata[1] = _mm256_set1_epi8(0x36); // 0 XOR 0x36 = 0x36 //tmp = _mm256_setr_epi32(_byteswap_ulong(0x77686174), _byteswap_ulong(0x20646F20), _byteswap_ulong(0x79612077), _byteswap_ulong(0x616E7420), _byteswap_ulong(0x666F7220), _byteswap_ulong(0x6E6F7468), _byteswap_ulong(0x696E673F), 0); // "what do ya want for nothing?" 28 znaků tmp = _mm256_setr_epi32(0x77686174, 0x20646F20, 0x79612077, 0x616E7420, 0x666F7220, 0x6E6F7468, 0x696E673F, 0); // "what do ya want for nothing?" 28 znaků indata[2] = _mm256_or_si256(tmp, _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, 0x80000000)); indata[3] = _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, 736); //počet bitů pri ipad hashování = (64 + 28) * 8 SHA1(indata); indata[0] = _mm256_xor_si256(key, _mm256_set1_epi8(0x5c)); indata[1] = _mm256_set1_epi8(0x5c); // 0 XOR 0x5c = 0x5c indata[2] = _mm256_or_si256(sha1res, _mm256_setr_epi32(0, 0, 0, 0, 0, 0x80000000, 0, 0)); indata[3] = _mm256_setr_epi32(0, 0, 0, 0, 0, 0, 0, 672); //počet bitů pro opad hashování = (64 + 20) * 8 SHA1(indata); auto stop = high_resolution_clock::now(); auto duration = duration_cast<microseconds>(stop - start); printf("Time = %lli (us DEC)\n", duration.count()); p256_hex_u32(sha1res); }