Kaip pasiekti teorinį maksimalų 4 FLOP per ciklą?

Kaip šiuolaikiniame „Intel x86-64“ procesoriuje bus pasiekta keturių slankiojo kablelio operacijų (dvigubo tikslumo) teorinė didžiausia charakteristika?

Kiek suprantu, daugeliui modernių „Intel“ procesorių reikia add tris SSE ciklus ir penkis ciklus mul žr., Pvz., „ Agner Fog“ instrukcijas ). Dėl vamzdynų sudarymo galima gauti vieno add per ciklą našumą, jei algoritmas turi mažiausiai tris nepriklausomus suvestinius duomenis. Kadangi tai pasakytina apie supakuotą addpd , taip pat „ addsd versijas, o SSE registruose gali būti du double , našumas gali pasiekti du srautus per ciklą.

Be to, atrodo (nors ir nemačiau jokių atitinkamų dokumentų apie tai), add ir mul galima atlikti lygiagrečiai, suteikiant teorinį maksimalų keturių flopų per ciklą našumą.

Tačiau aš negalėjau atkurti šios veiklos naudojant paprastą C / C ++ programą. Geriausias bandymas davė 2,7 flopo / ciklo. Jei kas nors gali prisidėti prie paprastos C / C ++ programos ar surinkėjo, kuris demonstruoja maksimalų našumą, kuris bus labai dėkingas.

Mano bandymas:

 #include <stdio.h> #include <stdlib.h> #include <math.h> #include <sys/time.h> double stoptime(void) { struct timeval t; gettimeofday( return (double) t.tv_sec + t.tv_usec/1000000.0; } double addmul(double add, double mul, int ops){ // Need to initialise differently otherwise compiler might optimise away double sum1=0.1, sum2=-0.1, sum3=0.2, sum4=-0.2, sum5=0.0; double mul1=1.0, mul2= 1.1, mul3=1.2, mul4= 1.3, mul5=1.4; int loops=ops/10; // We have 10 floating point operations inside the loop double expected = 5.0*add*loops + (sum1+sum2+sum3+sum4+sum5) + pow(mul,loops)*(mul1+mul2+mul3+mul4+mul5); for (int i=0; i<loops; i++) { mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul; sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add; } return sum1+sum2+sum3+sum4+sum5+mul1+mul2+mul3+mul4+mul5 - expected; } int main(int argc, char** argv) { if (argc != 2) { printf("usage: %s <num>\n", argv[0]); printf("number of operations: <num> millions\n"); exit(EXIT_FAILURE); } int n = atoi(argv[1]) * 1000000; if (n<=0) n=1000; double x = M_PI; double y = 1.0 + 1e-8; double t = stoptime(); x = addmul(x, y, n); t = stoptime() - t; printf("addmul:\t %.3f s, %.3f Gflops, res=%f\n", t, (double)n/t/1e9, x); return EXIT_SUCCESS; } 

Sudaryta su

 g++ -O2 -march=native addmul.cpp ; ./a.out 1000 

rodo „Intel Core i5-750“, 2,66 GHz rezultatus.

 addmul: 0.270 s, 3.707 Gflops, res=1.326463 

Tai reiškia, kad apie 1,4 ciklų per ciklą. Žvelgiant į surinkėjo kodą su g++ -S -O2 -march=native -masm=intel addmul.cpp pagrindinė kilpa man atrodo optimali:

 .L4: inc eax mulsd xmm8, xmm3 mulsd xmm7, xmm3 mulsd xmm6, xmm3 mulsd xmm5, xmm3 mulsd xmm1, xmm3 addsd xmm13, xmm2 addsd xmm12, xmm2 addsd xmm11, xmm2 addsd xmm10, xmm2 addsd xmm9, xmm2 cmp eax, ebx jne .L4 

Pakeitus skaliarines versijas su supakuotomis versijomis ( addpd ir mulpd ), dvigubai padidės flopų skaičius nekeičiant vykdymo laiko, todėl aš prarastu tik 2,8 flopus per ciklą. Ar yra paprastas pavyzdys, kuris pasiekia keturis flopus per ciklą?

Geros mažos programos Mistinė; čia yra mano rezultatai (vos kelios sekundės):

  • gcc -O2 -march=nocona : 5.6 Gflops nuo 10.66 Gflops (2,1 flops / ciklas)
  • cl /O2 , openmp išbraukta: 10.1 Gflops nuo 10.66 Gflops (3,8 flop / ciklas)

Viskas atrodo šiek tiek sudėtinga, bet mano išvados iki šiol:

  • gcc -O2 - mulpd , jei įmanoma, pakeičia nepriklausomų slankiojo kablelio operacijų addpd su pakaitinio addpd ir mulpd . Tas pats pasakytina ir apie gcc-4.6.2 -O2 -march=core2 .

  • gcc -O2 -march=nocona atrodo, išsaugo slankiojo kablelio operacijų tvarką, kaip apibrėžta C ++ šaltinyje.

  • cl /O2 , 64 bitų kompiliatorius iš „ Windows 7“ SDK, veikia automatiškai ir, atrodo, bando organizuoti operacijas taip, kad trijų addpd grupės pakaitomis su trimis mulpd (gerai, bent jau mano sistemoje ir mano paprastoje programoje) ).

  • Mano „ Core i5 750“ („ Nahlem“ architektūra ) nemėgsta kintančio papildymo ir daugialypės reikšmės, ir atrodo, kad negali veikti abiejų operacijų lygiagrečiai. Tačiau, jei suskirstyta į 3, jis staiga veikia kaip magija.

  • Kitos architektūros (galbūt „ Sandy Bridge“ ir kt.), Atrodo, gali atlikti / dauginti papildymą lygiagrečiai be problemų, jei jos pakaitomis sukuria kūrimo kodą.

  • Nors sunku atpažinti, mano sistemoje „ cl /O2 daug geriau veikia mano sistemos žemo lygio optimizavimo operacijose ir pasiekia beveik maksimalų našumą mažam C ++ pavyzdžiui. Aš matavau nuo 1,85-2,01 flops / ciklas (naudojamas laikrodis () Windows, kuris nėra toks tikslus. Manau, kad jums reikia naudoti geriausią laikmatį - dėka Mackie Messer).

  • Geriausias, kurį valdiau naudojant „ gcc yra rankiniu būdu išplėsti ir užsisakyti papildymus ir dauginimąsi trijų grupių grupėse. Su g++ -O2 -march=nocona addmul_unroll.cpp gaunu geriausiu 0.207s, 4.825 Gflops , kuris atitinka 1,8 flopus / ciklą, su kuriuo esu patenkintas.

C + + kode aš pakeičiau kilpą su

  for (int i=0; i<loops/3; i++) { mul1*=mul; mul2*=mul; mul3*=mul; sum1+=add; sum2+=add; sum3+=add; mul4*=mul; mul5*=mul; mul1*=mul; sum4+=add; sum5+=add; sum1+=add; mul2*=mul; mul3*=mul; mul4*=mul; sum2+=add; sum3+=add; sum4+=add; mul5*=mul; mul1*=mul; mul2*=mul; sum5+=add; sum1+=add; sum2+=add; mul3*=mul; mul4*=mul; mul5*=mul; sum3+=add; sum4+=add; sum5+=add; } 

Ir dabar, kaip atrodo

 .L4: mulsd xmm8, xmm3 mulsd xmm7, xmm3 mulsd xmm6, xmm3 addsd xmm13, xmm2 addsd xmm12, xmm2 addsd xmm11, xmm2 mulsd xmm5, xmm3 mulsd xmm1, xmm3 mulsd xmm8, xmm3 addsd xmm10, xmm2 addsd xmm9, xmm2 addsd xmm13, xmm2 ... 
509
05 дек. nustatė user1059432 05 dec. 2011-12-05 20:54 '11 prie 20:54 2011-12-05 20:54
@ 4 atsakymai

Aš padariau šią tikslią užduotį anksčiau. Tačiau tai buvo daugiausia energijos suvartojimo ir procesoriaus temperatūros matavimui. Šį kodą (kuris yra gana ilgas) mano Core i7 2600K optimaliai.

Svarbiausias dalykas čia yra didžiulis rankinių kilpų kiekis, taip pat pakaitinis dauginimas ir pridėjimas ...

Visą projektą galima rasti „GitHub“: https://github.com/Mysticial/Flops

Dėmesio:

Jei nuspręsite surinkti ir paleisti, atkreipkite dėmesį į procesoriaus temperatūrą.
Įsitikinkite, kad ji perkaitusi. Ir įsitikinkite, kad procesoriaus droselis neturi įtakos jūsų rezultatams!

Be to, nesu atsakingas už žalą, kuri gali atsirasti dėl šio kodo vykdymo.

Pastabos:

  • Šis kodas yra optimizuotas x64. x86 neturi pakankamai registrų, kuriuos reikia sukompiliuoti.
  • Šis kodas buvo gerai išbandytas „Visual Studio 2010/2012“ ir GCC 4.6.
    ICC 11 („Intel Compiler 11“) netikėtai susiduria su problemomis.
  • Tai taikoma prieš fma procesoriams. Norint pasiekti didžiausius FLOPS Intel Haswell ir AMD Bulldozer procesoriuose (ir vėlesnėse versijose), reikalingos FMA (Fused Multiply Add) instrukcijos. Tai nepatenka į šio testo taikymo sritį.

 #include <emmintrin.h> #include <omp.h> #include <iostream> using namespace std; typedef unsigned long long uint64; double test_dp_mac_SSE(double x,double y,uint64 iterations){ register __m128d r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,rA,rB,rC,rD,rE,rF; // Generate starting data. r0 = _mm_set1_pd(x); r1 = _mm_set1_pd(y); r8 = _mm_set1_pd(-0.0); r2 = _mm_xor_pd(r0,r8); r3 = _mm_or_pd(r0,r8); r4 = _mm_andnot_pd(r8,r0); r5 = _mm_mul_pd(r1,_mm_set1_pd(0.37796447300922722721)); r6 = _mm_mul_pd(r1,_mm_set1_pd(0.24253562503633297352)); r7 = _mm_mul_pd(r1,_mm_set1_pd(4.1231056256176605498)); r8 = _mm_add_pd(r0,_mm_set1_pd(0.37796447300922722721)); r9 = _mm_add_pd(r1,_mm_set1_pd(0.24253562503633297352)); rA = _mm_sub_pd(r0,_mm_set1_pd(4.1231056256176605498)); rB = _mm_sub_pd(r1,_mm_set1_pd(4.1231056256176605498)); rC = _mm_set1_pd(1.4142135623730950488); rD = _mm_set1_pd(1.7320508075688772935); rE = _mm_set1_pd(0.57735026918962576451); rF = _mm_set1_pd(0.70710678118654752440); uint64 iMASK = 0x800fffffffffffffull; __m128d MASK = _mm_set1_pd(*(double*) __m128d vONE = _mm_set1_pd(1.0); uint64 c = 0; while (c < iterations){ size_t i = 0; while (i < 1000){ // Here the meat - the part that really matters. r0 = _mm_mul_pd(r0,rC); r1 = _mm_add_pd(r1,rD); r2 = _mm_mul_pd(r2,rE); r3 = _mm_sub_pd(r3,rF); r4 = _mm_mul_pd(r4,rC); r5 = _mm_add_pd(r5,rD); r6 = _mm_mul_pd(r6,rE); r7 = _mm_sub_pd(r7,rF); r8 = _mm_mul_pd(r8,rC); r9 = _mm_add_pd(r9,rD); rA = _mm_mul_pd(rA,rE); rB = _mm_sub_pd(rB,rF); r0 = _mm_add_pd(r0,rF); r1 = _mm_mul_pd(r1,rE); r2 = _mm_sub_pd(r2,rD); r3 = _mm_mul_pd(r3,rC); r4 = _mm_add_pd(r4,rF); r5 = _mm_mul_pd(r5,rE); r6 = _mm_sub_pd(r6,rD); r7 = _mm_mul_pd(r7,rC); r8 = _mm_add_pd(r8,rF); r9 = _mm_mul_pd(r9,rE); rA = _mm_sub_pd(rA,rD); rB = _mm_mul_pd(rB,rC); r0 = _mm_mul_pd(r0,rC); r1 = _mm_add_pd(r1,rD); r2 = _mm_mul_pd(r2,rE); r3 = _mm_sub_pd(r3,rF); r4 = _mm_mul_pd(r4,rC); r5 = _mm_add_pd(r5,rD); r6 = _mm_mul_pd(r6,rE); r7 = _mm_sub_pd(r7,rF); r8 = _mm_mul_pd(r8,rC); r9 = _mm_add_pd(r9,rD); rA = _mm_mul_pd(rA,rE); rB = _mm_sub_pd(rB,rF); r0 = _mm_add_pd(r0,rF); r1 = _mm_mul_pd(r1,rE); r2 = _mm_sub_pd(r2,rD); r3 = _mm_mul_pd(r3,rC); r4 = _mm_add_pd(r4,rF); r5 = _mm_mul_pd(r5,rE); r6 = _mm_sub_pd(r6,rD); r7 = _mm_mul_pd(r7,rC); r8 = _mm_add_pd(r8,rF); r9 = _mm_mul_pd(r9,rE); rA = _mm_sub_pd(rA,rD); rB = _mm_mul_pd(rB,rC); i++; } // Need to renormalize to prevent denormal/overflow. r0 = _mm_and_pd(r0,MASK); r1 = _mm_and_pd(r1,MASK); r2 = _mm_and_pd(r2,MASK); r3 = _mm_and_pd(r3,MASK); r4 = _mm_and_pd(r4,MASK); r5 = _mm_and_pd(r5,MASK); r6 = _mm_and_pd(r6,MASK); r7 = _mm_and_pd(r7,MASK); r8 = _mm_and_pd(r8,MASK); r9 = _mm_and_pd(r9,MASK); rA = _mm_and_pd(rA,MASK); rB = _mm_and_pd(rB,MASK); r0 = _mm_or_pd(r0,vONE); r1 = _mm_or_pd(r1,vONE); r2 = _mm_or_pd(r2,vONE); r3 = _mm_or_pd(r3,vONE); r4 = _mm_or_pd(r4,vONE); r5 = _mm_or_pd(r5,vONE); r6 = _mm_or_pd(r6,vONE); r7 = _mm_or_pd(r7,vONE); r8 = _mm_or_pd(r8,vONE); r9 = _mm_or_pd(r9,vONE); rA = _mm_or_pd(rA,vONE); rB = _mm_or_pd(rB,vONE); c++; } r0 = _mm_add_pd(r0,r1); r2 = _mm_add_pd(r2,r3); r4 = _mm_add_pd(r4,r5); r6 = _mm_add_pd(r6,r7); r8 = _mm_add_pd(r8,r9); rA = _mm_add_pd(rA,rB); r0 = _mm_add_pd(r0,r2); r4 = _mm_add_pd(r4,r6); r8 = _mm_add_pd(r8,rA); r0 = _mm_add_pd(r0,r4); r0 = _mm_add_pd(r0,r8); // Prevent Dead Code Elimination double out = 0; __m128d temp = r0; out += ((double*) out += ((double*) return out; } void test_dp_mac_SSE(int tds,uint64 iterations){ double *sum = (double*)malloc(tds * sizeof(double)); double start = omp_get_wtime(); #pragma omp parallel num_threads(tds) { double ret = test_dp_mac_SSE(1.1,2.1,iterations); sum[omp_get_thread_num()] = ret; } double secs = omp_get_wtime() - start; uint64 ops = 48 * 1000 * iterations * tds * 2; cout << "Seconds = " << secs << endl; cout << "FP Ops = " << ops << endl; cout << "FLOPs = " << ops / secs << endl; double out = 0; int c = 0; while (c < tds){ out += sum[c++]; } cout << "sum = " << out << endl; cout << endl; free(sum); } int main(){ // (threads, iterations) test_dp_mac_SSE(8,10000000); system("pause"); } 

Išvestis (1 srautas, iteracijos 10 000 000) - parengta naudojant „Visual Studio 2010 SP1“ - x64 leidimą:

 Seconds = 55.5104 FP Ops = 960000000000 FLOPs = 1.7294e+010 sum = 2.22652 

Prietaisas Core i7 2600K @ 4.4 GHz. Teorinė SSE smailė yra 4 flopai * 4.4 GHz = 17,6 GFlops . Šis kodas pasiekia 17,3 GFlops - ne blogai.

Išvestis (8 gijos, iteracijos 10 000 000) - parengta naudojant „Visual Studio 2010 SP1“ - x64 leidimą:

 Seconds = 117.202 FP Ops = 7680000000000 FLOPs = 6.55279e+010 sum = 17.8122 

Teorinė SSE smailė yra 4 flopai * 4 šerdys * 4,4 GHz = 70,4 GFlops. Faktiniai 65,5 GFlops .


Paimkime dar vieną žingsnį. Avx ...

 #include <immintrin.h> #include <omp.h> #include <iostream> using namespace std; typedef unsigned long long uint64; double test_dp_mac_AVX(double x,double y,uint64 iterations){ register __m256d r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,rA,rB,rC,rD,rE,rF; // Generate starting data. r0 = _mm256_set1_pd(x); r1 = _mm256_set1_pd(y); r8 = _mm256_set1_pd(-0.0); r2 = _mm256_xor_pd(r0,r8); r3 = _mm256_or_pd(r0,r8); r4 = _mm256_andnot_pd(r8,r0); r5 = _mm256_mul_pd(r1,_mm256_set1_pd(0.37796447300922722721)); r6 = _mm256_mul_pd(r1,_mm256_set1_pd(0.24253562503633297352)); r7 = _mm256_mul_pd(r1,_mm256_set1_pd(4.1231056256176605498)); r8 = _mm256_add_pd(r0,_mm256_set1_pd(0.37796447300922722721)); r9 = _mm256_add_pd(r1,_mm256_set1_pd(0.24253562503633297352)); rA = _mm256_sub_pd(r0,_mm256_set1_pd(4.1231056256176605498)); rB = _mm256_sub_pd(r1,_mm256_set1_pd(4.1231056256176605498)); rC = _mm256_set1_pd(1.4142135623730950488); rD = _mm256_set1_pd(1.7320508075688772935); rE = _mm256_set1_pd(0.57735026918962576451); rF = _mm256_set1_pd(0.70710678118654752440); uint64 iMASK = 0x800fffffffffffffull; __m256d MASK = _mm256_set1_pd(*(double*) __m256d vONE = _mm256_set1_pd(1.0); uint64 c = 0; while (c < iterations){ size_t i = 0; while (i < 1000){ // Here the meat - the part that really matters. r0 = _mm256_mul_pd(r0,rC); r1 = _mm256_add_pd(r1,rD); r2 = _mm256_mul_pd(r2,rE); r3 = _mm256_sub_pd(r3,rF); r4 = _mm256_mul_pd(r4,rC); r5 = _mm256_add_pd(r5,rD); r6 = _mm256_mul_pd(r6,rE); r7 = _mm256_sub_pd(r7,rF); r8 = _mm256_mul_pd(r8,rC); r9 = _mm256_add_pd(r9,rD); rA = _mm256_mul_pd(rA,rE); rB = _mm256_sub_pd(rB,rF); r0 = _mm256_add_pd(r0,rF); r1 = _mm256_mul_pd(r1,rE); r2 = _mm256_sub_pd(r2,rD); r3 = _mm256_mul_pd(r3,rC); r4 = _mm256_add_pd(r4,rF); r5 = _mm256_mul_pd(r5,rE); r6 = _mm256_sub_pd(r6,rD); r7 = _mm256_mul_pd(r7,rC); r8 = _mm256_add_pd(r8,rF); r9 = _mm256_mul_pd(r9,rE); rA = _mm256_sub_pd(rA,rD); rB = _mm256_mul_pd(rB,rC); r0 = _mm256_mul_pd(r0,rC); r1 = _mm256_add_pd(r1,rD); r2 = _mm256_mul_pd(r2,rE); r3 = _mm256_sub_pd(r3,rF); r4 = _mm256_mul_pd(r4,rC); r5 = _mm256_add_pd(r5,rD); r6 = _mm256_mul_pd(r6,rE); r7 = _mm256_sub_pd(r7,rF); r8 = _mm256_mul_pd(r8,rC); r9 = _mm256_add_pd(r9,rD); rA = _mm256_mul_pd(rA,rE); rB = _mm256_sub_pd(rB,rF); r0 = _mm256_add_pd(r0,rF); r1 = _mm256_mul_pd(r1,rE); r2 = _mm256_sub_pd(r2,rD); r3 = _mm256_mul_pd(r3,rC); r4 = _mm256_add_pd(r4,rF); r5 = _mm256_mul_pd(r5,rE); r6 = _mm256_sub_pd(r6,rD); r7 = _mm256_mul_pd(r7,rC); r8 = _mm256_add_pd(r8,rF); r9 = _mm256_mul_pd(r9,rE); rA = _mm256_sub_pd(rA,rD); rB = _mm256_mul_pd(rB,rC); i++; } // Need to renormalize to prevent denormal/overflow. r0 = _mm256_and_pd(r0,MASK); r1 = _mm256_and_pd(r1,MASK); r2 = _mm256_and_pd(r2,MASK); r3 = _mm256_and_pd(r3,MASK); r4 = _mm256_and_pd(r4,MASK); r5 = _mm256_and_pd(r5,MASK); r6 = _mm256_and_pd(r6,MASK); r7 = _mm256_and_pd(r7,MASK); r8 = _mm256_and_pd(r8,MASK); r9 = _mm256_and_pd(r9,MASK); rA = _mm256_and_pd(rA,MASK); rB = _mm256_and_pd(rB,MASK); r0 = _mm256_or_pd(r0,vONE); r1 = _mm256_or_pd(r1,vONE); r2 = _mm256_or_pd(r2,vONE); r3 = _mm256_or_pd(r3,vONE); r4 = _mm256_or_pd(r4,vONE); r5 = _mm256_or_pd(r5,vONE); r6 = _mm256_or_pd(r6,vONE); r7 = _mm256_or_pd(r7,vONE); r8 = _mm256_or_pd(r8,vONE); r9 = _mm256_or_pd(r9,vONE); rA = _mm256_or_pd(rA,vONE); rB = _mm256_or_pd(rB,vONE); c++; } r0 = _mm256_add_pd(r0,r1); r2 = _mm256_add_pd(r2,r3); r4 = _mm256_add_pd(r4,r5); r6 = _mm256_add_pd(r6,r7); r8 = _mm256_add_pd(r8,r9); rA = _mm256_add_pd(rA,rB); r0 = _mm256_add_pd(r0,r2); r4 = _mm256_add_pd(r4,r6); r8 = _mm256_add_pd(r8,rA); r0 = _mm256_add_pd(r0,r4); r0 = _mm256_add_pd(r0,r8); // Prevent Dead Code Elimination double out = 0; __m256d temp = r0; out += ((double*) out += ((double*) out += ((double*) out += ((double*) return out; } void test_dp_mac_AVX(int tds,uint64 iterations){ double *sum = (double*)malloc(tds * sizeof(double)); double start = omp_get_wtime(); #pragma omp parallel num_threads(tds) { double ret = test_dp_mac_AVX(1.1,2.1,iterations); sum[omp_get_thread_num()] = ret; } double secs = omp_get_wtime() - start; uint64 ops = 48 * 1000 * iterations * tds * 4; cout << "Seconds = " << secs << endl; cout << "FP Ops = " << ops << endl; cout << "FLOPs = " << ops / secs << endl; double out = 0; int c = 0; while (c < tds){ out += sum[c++]; } cout << "sum = " << out << endl; cout << endl; free(sum); } int main(){ // (threads, iterations) test_dp_mac_AVX(8,10000000); system("pause"); } 

Išvestis (1 srautas, iteracijos 10 000 000) - parengta naudojant „Visual Studio 2010 SP1“ - x64 leidimą:

 Seconds = 57.4679 FP Ops = 1920000000000 FLOPs = 3.34099e+010 sum = 4.45305 

Teorinis AVX piko yra 8 flopai * 4,4 GHz = 35,2 GFlops . Iš tiesų, 33,4 GFlops .

Išvestis (8 gijos, iteracijos 10 000 000) - parengta naudojant „Visual Studio 2010 SP1“ - x64 leidimą:

 Seconds = 111.119 FP Ops = 15360000000000 FLOPs = 1.3823e+011 sum = 35.6244 

Teorinis AVX piko yra 8 flopai * 4 šerdys * 4,4 GHz = 140,8 GFlops. Faktiniai 138.2 GFlops .


Dabar paaiškinkite:

Svarbi veiklos dalis, matyt, yra 48 instrukcijos vidinėje kilpoje. Jūs pastebėsite, kad jis suskirstytas į 4 blokus po 12 instrukcijų. Kiekvienas iš šių 12 instrukcijų blokų yra visiškai nepriklausomas vienas nuo kito ir vidutiniškai atlieka 6 ciklus.

Taigi tarp išleidimo yra 12 instrukcijų ir 6 ciklai. Dauginimo vėlavimas yra 5 ciklai, todėl pakanka išvengti latentinių kioskų.

Norint, kad duomenys būtų perkrauti / perpildyti, būtina normalizavimo pakopa. Tai būtina, nes kodo kodas lėtai padidins / sumažins duomenų dydį.

Taigi jūs iš tikrųjų galite geriau nei tai padaryti, jei tiesiog naudosite visus nulius ir atsikratysite normalizavimo žingsnio. Tačiau, kadangi aš parašiau etaloną energijos suvartojimui ir temperatūrai matuoti, turėjau įsitikinti, kad flopai buvo „tikri“, o ne nuliai , nes pavaros gali turėti specialų apdorojimo dėklą nuliams, kurie sunaudoja mažiau energijos ir gamina mažiau šilumos.


Papildomi rezultatai:

  • „Intel Core i7 920 @ 3.5 GHz“
  • „Windows 7 Ultimate x64“
  • „Visual Studio 2010 SP1“ - x64 leidimas

Temos: 1

 Seconds = 72.1116 FP Ops = 960000000000 FLOPs = 1.33127e+010 sum = 2.22652 

Teorinis didžiausias SSE: 4 flops * 3,5 GHz = 14,0 GFlops . Iš tiesų, 13,3 GFlops .

Temos: 8

 Seconds = 149.576 FP Ops = 7680000000000 FLOPs = 5.13452e+010 sum = 17.8122 

Teorinis smailės SSE: 4 flopai * 4 šerdys * 3,5 GHz = 56,0 GFlops . Iš tiesų, 51,3 GFlops .

Mano procesoriaus laikas pasiekė 76C su daugiapakopiu paleidimu! Jei naudojate juos, įsitikinkite, kad CPU droselis neturi įtakos rezultatams.


  • 2 x „Intel Xeon X5482 Harpertown @ 3.2 GHz“
  • Ubuntu Linux 10 x64
  • GCC 4.5.2 x64 - (-O2 -msse3 -fopenmp)

Temos: 1

 Seconds = 78.3357 FP Ops = 960000000000 FLOPs = 1.22549e+10 sum = 2.22652 

Teorinė SSE smailė: 4 flopai * 3.2 GHz = 12,8 GFlops . Iš tikrųjų 12,3 GFlops .

Temos: 8

 Seconds = 78.4733 FP Ops = 7680000000000 FLOPs = 9.78676e+10 sum = 17.8122 

Teorinis didžiausias SSE: 4 flopai * 8 šerdys * 3,2 GHz = 102,4 GFlops . Iš tiesų, 97,9 GFlops .

415
05 дек. Atsakymas pateikiamas Mysticial 05 Dec 2011-12-05 23:43 '11, 11:43 val. 2011-12-05 23:43

„Intel“ architektūroje, kurią žmonės dažnai pamiršo, yra taškas: siunčiantys prievadai yra padalinti į „Int“ ir „FP / SIMD“. Tai reiškia, kad jūs gaunate tik tam tikrą skaičių FP / SIMD paketų, kol ciklo logika sukuria burbulus plūduriuojančio taško sraute. Mystikas iš savo kodo gavo daugiau nesėkmių, nes jis savo ilgesniame kampe naudojo ilgesnius žingsnius.

Jei pažvelgsite į „Nehalem“ / „Sandy Bridge“ architektūrą, čia http://www.realworldtech.com/page.cfm?ArticleID=RWT091810191937> yra visiškai aišku, kas vyksta.

border=0

Priešingai, turėtų būti lengviau pasiekti maksimalų našumą AMD (buldozeris), nes INT ir FP / SIMD kanaluose yra atskiri problemos uostai su savo planuotoju.

Tai teoriškai, nes aš neišbandau nė vieno iš šių procesorių.

27
06 дек. Patrick Schlüter atsakymas gruodžio 06 d 2011-12-06 19:05 '11, 19:05, 2011-12-06 19:05

Žinoma, filialai gali trukdyti išlaikyti maksimalią teorinę veiklą. Ar matote skirtumą, jei rankiniu būdu atliksite apsisukimo ciklą? Pvz., Jei kiekvienai kilpos iteracijai įdėjote 5 ar 10 kartų daugiau galimybių:

 for(int i=0; i<loops/5; i++) { mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul; sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add; mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul; sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add; mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul; sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add; mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul; sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add; mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul; sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add; } 
14
05 дек. atsakymas pateikiamas TJD 05 dec. 2011-12-05 21:04 '11, 21:04 2011-12-05 21:04

Naudojant Intels ICC versiją 11.1 „Intel Core 2 Duo“ 2,4 GHz, gaunu

 Macintosh:~ mackie$ icc -O3 -mssse3 -oaddmul addmul.cc  ./addmul 1000 addmul: 0.105 s, 9.525 Gflops, res=0.000000 Macintosh:~ mackie$ icc -v Version 11.1 

Tai labai artimas idealiam 9.6 „Gflops“.

EDIT:

Oi, pažvelgę ​​į statymo kodą, atrodo, kad icc ne tik vektorizavo dauginimą, bet ir ištraukė papildymus iš kilpos. Priverčiant griežtesnį fp semantiką, kodas nebebus vektorizuojamas:

 Macintosh:~ mackie$ icc -O3 -mssse3 -oaddmul addmul.cc -fp-model precise  ./addmul 1000 addmul: 0.516 s, 1.938 Gflops, res=1.326463 

EDIT2:

Pagal prašymą:

 Macintosh:~ mackie$ c> 

Vidinis „c>

  .align 4, 0x90 LBB2_4: ## =>This Inner Loop Header: Depth=1 addsd %xmm2, %xmm3 addsd %xmm2, %xmm14 addsd %xmm2, %xmm5 addsd %xmm2, %xmm1 addsd %xmm2, %xmm4 mulsd %xmm2, %xmm0 mulsd %xmm2, %xmm6 mulsd %xmm2, %xmm7 mulsd %xmm2, %xmm11 mulsd %xmm2, %xmm13 incl %eax cmpl %r14d, %eax jl LBB2_4 

EDIT3:

Galiausiai, du sakiniai: pirma, jei jums patinka šis lyginamosios analizės tipas, apsvarstykite galimybę naudoti komandą rdtsc istead gettimeofday(2) . Tai daug tikslesnė ir suteikia laiko cikliuose, kurie paprastai jus domina. Gcc ir draugams galite jį apibrėžti taip:

 #include <stdint.h> static __inline__ uint64_t rdtsc(void) { uint64_t rval; __asm__ volatile ("rdtsc" : "=A" (rval)); return rval; } 

Antra, turite kelis kartus paleisti bandymų programą ir naudoti tik geriausius rezultatus. Šiuolaikinėse operacinėse sistemose daug kas vyksta lygiagrečiai, procesorius gali būti mažos galios režimu ir tt Programos vykdymas kelis kartus suteikia rezultatą, kuris yra arčiau idealaus atvejo.

6
05 дек. Atsakymą pateikė Mackie Messer. 2011-12-05 23:19 '11 11:19 PM 2011-12-05 23:19