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ü add
ve a mul
iç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 addpd
hem paketlenmiş hem de skaler addsd
versiyonlar 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) add
ve mul
dö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.cpp
ana 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 ( addpd
ve 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 -O2
Değişkenaddpd
vemulpd
mü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=nocona
C ++ 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 üçüaddpd
ile 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 /O2
iç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
gcc
elle açma ve üç kişilik gruplar halinde eklemeler ve çarpmalar düzenlemek oldu. İleg++ -O2 -march=nocona addmul_unroll.cpp
ben en iyi olsun0.207s, 4.825 Gflops
1.8 hangi karşılık flop / Ben şimdi oldukça mutluyum döngüsü.
C ++ kodunda for
dö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?
-O3
sağlayan, gcc için -ftree-vectorize
? Belki de -funroll-loops
gerç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-loops
muhtemelen 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ı.