Modern x86-64 Intel CPU'da çevrim başına 4 kayan nokta işleminin (çift kesinlik) teorik tepe performansı nasıl elde edilebilir?
Anladığım kadarıyla , modern Intel CPU'ların çoğunda bir SSE için üç döngü addve a muliçin beş döngü gerekiyor (örneğin Agner Sis'in 'Talimat Tabloları' na bakın ). Boru hattı nedeniyle add, algoritmanın en az üç bağımsız toplamı varsa, döngü başına bir verim elde edilebilir . Bu addpdhem paketlenmiş hem de skaler addsdversiyonlar ve SSE kayıtları için iki tane içerebildiğinden double, verim, döngü başına iki flop kadar olabilir.
Ayrıca, (bununla ilgili herhangi bir uygun belge görmemiş olsam da) addve muldöngüleri, döngü başına dört flop teorik maksimum verim vererek paralel olarak yürütülebilir.
Ancak, bu performansı basit bir C / C ++ programı ile çoğaltamadım. En iyi denemem yaklaşık 2.7 flop / döngü ile sonuçlandı. Herkes büyük bir takdir en yüksek performansı gösteren basit bir C / C ++ veya montaj programı katkıda bulunabilir.
Girişimim:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>
double stoptime(void) {
struct timeval t;
gettimeofday(&t,NULL);
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;
}
İle derlendi
g++ -O2 -march=native addmul.cpp ; ./a.out 1000
Intel Core i5-750, 2,66 GHz'de aşağıdaki çıkışı üretir.
addmul: 0.270 s, 3.707 Gflops, res=1.326463
Yani, döngü başına sadece 1.4 flop. Montajcı koduna g++ -S -O2 -march=native -masm=intel addmul.cppana döngü ile bakmak
benim için uygun görünüyor:
.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
Paketlenmiş sürümleri ( addpdve mulpd) ile skaler sürümlerini değiştirmek, yürütme süresini değiştirmeden flop sayısını iki katına çıkarır ve böylece döngü başına 2,8 floptan daha kısa olurum. Her döngüde dört flop sağlayan basit bir örnek var mı?
Mysticial tarafından güzel küçük program; benim sonuçlarım (gerçi sadece birkaç saniye çalıştırın):
gcc -O2 -march=nocona: 10,66 Gflop'tan 5,6 Gflop (2,1 flop / döngü)cl /O2, openmp çıkarıldı: 10,66 Gflop'tan 10,1 Gflop (3,8 flop / döngü)
Her şey biraz karmaşık gibi görünüyor, ama şu ana kadarki sonuçlarım:
gcc -O2Değişkenaddpdvemulpdmümkünse amacıyla bağımsız kayan nokta işlemlerinin sırasını değiştirir . Aynı şey için de geçerlidirgcc-4.6.2 -O2 -march=core2.gcc -O2 -march=noconaC ++ kaynağında tanımlandığı gibi kayan nokta işlemlerinin sırasını koruyor gibi görünüyor.cl /O2, Windows 7 için SDK'nın 64 bit derleyicisi otomatik olarak döngü açma işlemini gerçekleştirir ve üç grubun üçüaddpdile dönüşümlü olarak çalışmasını dener ve düzenler gibi görünüyormulpd(en azından sistemimde ve basit programımda) .Benim Çekirdek 750 i5 ( Nehalem mimarisini alternatif eklenti en ve mul yıllardan gibi değil) ve paralel olarak her iki işlemleri çalıştırmak mümkün görünüyor. Ancak, 3'lerde gruplandırılmışsa aniden sihir gibi çalışır.
Diğer mimariler (muhtemelen Sandy Bridge ve diğerleri) montaj kodunda dönüşümlü olarak eklenti / mul komutunu sorunsuz bir şekilde yürütebilirler.
Kabul etmek zor olsa da, sistemimde sistemim
cl /O2için düşük seviyeli optimizasyon işlemlerinde çok daha iyi bir iş çıkarıyor ve yukarıdaki küçük C ++ örneği için zirveye yakın performans elde ediyor. 1.85-2.01 flop / döngü arasında ölçtüm (Windows'ta saat () kullandım, bu kesin değil. Sanırım, daha iyi bir zamanlayıcı kullanmanız gerekiyor - teşekkürler Mackie Messer).En iyi yönettiğim
gccelle açma ve üç kişilik gruplar halinde eklemeler ve çarpmalar düzenlemek oldu. İleg++ -O2 -march=nocona addmul_unroll.cppben en iyi olsun0.207s, 4.825 Gflops1.8 hangi karşılık flop / Ben şimdi oldukça mutluyum döngüsü.
C ++ kodunda fordöngü ile değiştirdim
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;
}
Ve montaj şimdi benziyor
.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
...
-funroll-loops). Gcc 4.4.1 ve 4.6.2 sürümleriyle denediniz, ancak asm çıkışı iyi görünüyor mu?
-O3sağlayan, gcc için -ftree-vectorize? Belki de -funroll-loopsgerçekten gerekli olup olmadığını yapmama rağmen birlikte . Derleyicilerden biri vektörleştirme / açma yapıyorsa, diğeri yapamadığı için değil, aynı zamanda söylenmediği için karşılaştırma biraz haksız görünüyor.
-funroll-loopsmuhtemelen denemek için bir şey. Ama bence -ftree-vectorizeönemli değil. OP sadece 1 mul + 1 ekleme talimatını / döngüsünü sürdürmeye çalışıyor. Talimatlar skaler veya vektör olabilir - gecikme ve verim aynı olduğundan önemli değildir. Eğer skaler SSE ile 2 / döngüyü sürdürebilirseniz, bunları vektör SSE ile değiştirebilirsiniz ve 4 flop / döngü elde edersiniz. Cevabımda sadece SSE -> AVX'ten bunu yaptım. Tüm SSE'yi AVX ile değiştirdim - aynı gecikmeler, aynı verim, flopların 2 katı.