İlk olarak 2009'da GCC'nin (en azından projelerimde ve makinelerimde) hız ( veya ) yerine size ( -Os
) için optimize edersem fark edilir derecede daha hızlı kod üretme eğiliminde olduğunu fark ettim ve nedenini merak ediyorum.-O2
-O3
Bu şaşırtıcı davranışı gösteren ve burada gönderilecek kadar küçük bir kod (oldukça aptal) oluşturmayı başardık.
const int LOOP_BOUND = 200000000;
__attribute__((noinline))
static int add(const int& x, const int& y) {
return x + y;
}
__attribute__((noinline))
static int work(int xval, int yval) {
int sum(0);
for (int i=0; i<LOOP_BOUND; ++i) {
int x(xval+sum);
int y(yval+sum);
int z = add(x, y);
sum += z;
}
return sum;
}
int main(int , char* argv[]) {
int result = work(*argv[1], *argv[2]);
return result;
}
Derlediğimde -Os
, bu programı yürütmek için 0.38 s -O2
veya derlenmişse 0.44 s sürer -O3
. Bu süreler tutarlı bir şekilde ve neredeyse hiç gürültü olmadan elde edilir (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M).
(Güncelleme: Tüm montaj kodunu GitHub'a taşıdım : Gönderiyi şişirdiler ve fno-align-*
bayraklar aynı etkiye sahip olduğu için sorulara çok az değer kattılar.)
Burada ile montaj oluşturulur -Os
ve -O2
.
Ne yazık ki, montaj benim anlayış çok hiç bir fikrim yok bu yüzden, sınırlı olmadığını önümüzdeki yaptığı doğru ne olduğunu: Ben montaj yakaladı -O2
ve montaj içine tüm farklılıkları birleşti -Os
hariç.p2align
hatları, yol burada . Bu kod hala 0.38s'de çalışır ve tek fark şeydir .p2align
.
Doğru tahmin edersem, bunlar yığın hizalaması için dolgulardır. Göre NOPs ile GCC ped fonksiyonlarını yapar Neden? kodun daha hızlı çalışması umuduyla yapılır, ancak görünüşe göre bu optimizasyon benim durumumda geri tepti.
Bu durumda suçlu olan dolgu var mı? Neden ve nasıl?
Yaptığı gürültü, zamanlama mikro optimizasyonlarını imkansız hale getiriyor.
C veya C ++ kaynak kodunda mikro optimizasyonlar (yığın hizalamasıyla ilgisiz) yaptığımda bu tür kazara şanslı / şanssız hizalamaların parazit yapmadığından nasıl emin olabilirim?
GÜNCELLEME:
Pascal Cuoq'un cevabını takiben hizalamalarla biraz uğraştım . Gcc'ye geçerek -O2 -fno-align-functions -fno-align-loops
, hepsi .p2align
derlemeden gider ve oluşturulan yürütülebilir dosya 0,38 saniyede çalışır. Göre gcc belgelerinde :
-Os tüm -O2 optimizasyonlarını etkinleştirir [ama] -Os aşağıdaki optimizasyon bayraklarını devre dışı bırakır:
-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays
Yani, hemen hemen (yanlış) bir hizalama sorunu gibi görünüyor.
Hala Marat Dükhan'ın cevabında-march=native
önerildiği gibi şüpheliyim . Sadece bu (yanlış) hizalama sorununa müdahale etmediğine ikna olmadım; makinem üzerinde kesinlikle bir etkisi yok. (Yine de cevabını iptal ettim.)
GÜNCELLEME 2:
-Os
Resmi çıkarabiliriz . Aşağıdaki süreler derlenerek elde edilir.
-O2 -fno-omit-frame-pointer
0.37s-O2 -fno-align-functions -fno-align-loops
0.37s-S -O2
add()
sonrawork()
0.37s sonra montaj el ile hareket-O2
0.44s
Bana öyle add()
geliyor ki çağrı sitesinden uzaklığı çok önemli. Denedim perf
, ama çıktısı perf stat
ve perf report
bana çok az mantıklı. Ancak, bundan sadece bir tutarlı sonuç alabilirim:
-O2
:
602,312,864 stalled-cycles-frontend # 0.00% frontend cycles idle
3,318 cache-misses
0.432703993 seconds time elapsed
[...]
81.23% a.out a.out [.] work(int, int)
18.50% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
100.00 ¦ lea (%rdi,%rsi,1),%eax
¦ }
¦ ? retq
[...]
¦ int z = add(x, y);
1.93 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
79.79 ¦ add %eax,%ebx
Şunun için fno-align-*
:
604,072,552 stalled-cycles-frontend # 0.00% frontend cycles idle
9,508 cache-misses
0.375681928 seconds time elapsed
[...]
82.58% a.out a.out [.] work(int, int)
16.83% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
51.59 ¦ lea (%rdi,%rsi,1),%eax
¦ }
[...]
¦ __attribute__((noinline))
¦ static int work(int xval, int yval) {
¦ int sum(0);
¦ for (int i=0; i<LOOP_BOUND; ++i) {
¦ int x(xval+sum);
8.20 ¦ lea 0x0(%r13,%rbx,1),%edi
¦ int y(yval+sum);
¦ int z = add(x, y);
35.34 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
39.48 ¦ add %eax,%ebx
¦ }
Şunun için -fno-omit-frame-pointer
:
404,625,639 stalled-cycles-frontend # 0.00% frontend cycles idle
10,514 cache-misses
0.375445137 seconds time elapsed
[...]
75.35% a.out a.out [.] add(int const&, int const&) [clone .isra.0] ¦
24.46% a.out a.out [.] work(int, int)
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
18.67 ¦ push %rbp
¦ return x + y;
18.49 ¦ lea (%rdi,%rsi,1),%eax
¦ const int LOOP_BOUND = 200000000;
¦
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ mov %rsp,%rbp
¦ return x + y;
¦ }
12.71 ¦ pop %rbp
¦ ? retq
[...]
¦ int z = add(x, y);
¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
29.83 ¦ add %eax,%ebx
Görünüşe göre add()
yavaş durumda çağrıda duruyoruz .
Ben inceledim her şeyiperf -e
benim makinede tükürür olabilir; sadece yukarıda verilen istatistikler değil.
Aynı yürütülebilir dosya stalled-cycles-frontend
için yürütme süresi ile doğrusal korelasyon gösterir; Bu kadar açık bir şekilde ilişkilendirilecek başka bir şey fark etmedim. ( stalled-cycles-frontend
Farklı yürütülebilir dosyaları karşılaştırmak benim için anlamlı değil.)
İlk yorum olarak geldi önbellek özledim dahil. perf
Sadece yukarıda verilenlerle değil , makinemde ölçülebilen tüm önbellek hatalarını inceledim . Önbellek özlüyor çok gürültülü ve yürütme süreleri ile çok az korelasyon gösterir.