Saya pertama kali memperhatikan pada tahun 2009 bahwa GCC (setidaknya pada proyek saya dan pada mesin saya) memiliki kecenderungan untuk menghasilkan kode yang lebih cepat jika saya mengoptimalkan untuk ukuran ( -Os
) daripada kecepatan ( -O2
atau -O3
), dan saya telah bertanya-tanya sejak mengapa.
Saya telah berhasil membuat kode (agak konyol) yang menunjukkan perilaku mengejutkan ini dan cukup kecil untuk diposting di sini.
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;
}
Jika saya mengompilasinya -Os
, dibutuhkan 0,38 detik untuk menjalankan program ini, dan 0,44 detik jika dikompilasi dengan -O2
atau -O3
. Waktu-waktu ini diperoleh secara konsisten dan praktis tanpa noise (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M).
(Pembaruan: Saya telah memindahkan semua kode assembly ke GitHub : Mereka membuat postingan membengkak dan tampaknya menambah sedikit nilai pada pertanyaan karena fno-align-*
flag - flag memiliki efek yang sama.)
Ini adalah rakitan yang dihasilkan dengan -Os
dan -O2
.
Sayangnya, pemahaman saya tentang perakitan sangat terbatas, jadi saya tidak tahu apakah apa yang saya lakukan berikutnya adalah benar: Saya meraih perakitan untuk -O2
dan bergabung semua perbedaan ke dalam perakitan untuk -Os
kecuali yang .p2align
garis, hasil di sini . Kode ini masih berjalan di 0.38s dan satu-satunya perbedaan adalah .p2align
barang.
Jika saya menebak dengan benar, ini adalah paddings untuk perataan tumpukan. Menurut Mengapa fungsi pad GCC dengan NOP? itu dilakukan dengan harapan kode akan berjalan lebih cepat, tetapi ternyata optimasi ini menjadi bumerang dalam kasus saya.
Apakah ini padding yang menjadi pelakunya? Kenapa dan bagaimana?
Kebisingan yang dihasilkannya membuat pengoptimalan mikro menjadi tidak mungkin.
Bagaimana saya bisa memastikan bahwa keberpihakan yang tidak disengaja / tidak beruntung tersebut tidak mengganggu ketika saya melakukan optimasi mikro (tidak terkait dengan penumpukan keselarasan) pada kode sumber C atau C ++?
MEMPERBARUI:
Mengikuti jawaban Pascal Cuoq, saya mengutak-atik sedikit dengan keberpihakan. Dengan meneruskan -O2 -fno-align-functions -fno-align-loops
ke gcc, semua .p2align
hilang dari perakitan dan menjalankan eksekusi yang dihasilkan di 0,38s. Menurut dokumentasi gcc :
-Os memungkinkan semua -O2 optimasi [tetapi] -Os menonaktifkan flag optimasi berikut:
-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays
Jadi, sepertinya masalah pelurusan (mis).
Saya masih skeptis tentang -march=native
seperti yang disarankan dalam jawaban Marat Dukhan . Saya tidak yakin bahwa itu tidak hanya mengganggu masalah keberpihakan (salah) ini; sama sekali tidak berpengaruh pada mesin saya. (Namun demikian, saya meningkatkan jawabannya.)
PEMBARUAN 2:
Kita bisa mengambil -Os
gambarnya. Waktu berikut diperoleh dengan kompilasi dengan
-O2 -fno-omit-frame-pointer
0,37 detik-O2 -fno-align-functions -fno-align-loops
0,37 detik-S -O2
kemudian secara manual memindahkan perakitanadd()
setelahwork()
0,37s-O2
0,44
Sepertinya bagi saya jarak dari add()
situs panggilan sangat penting. Saya sudah mencoba perf
, tetapi hasil dari perf stat
dan perf report
sangat tidak masuk akal bagi saya. Namun, saya hanya bisa mendapatkan satu hasil yang konsisten dari itu:
-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
Untuk 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
¦ }
Untuk -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
Sepertinya kita menunda panggilan untuk add()
dalam kasus lambat.
Saya telah memeriksa segala sesuatu yang perf -e
dapat dimuntahkan di mesin saya; bukan hanya statistik yang diberikan di atas.
Untuk executable yang sama, stalled-cycles-frontend
menunjukkan korelasi linier dengan waktu eksekusi; Saya tidak memperhatikan hal lain yang akan berkorelasi begitu jelas. (Membandingkan stalled-cycles-frontend
untuk executable yang berbeda tidak masuk akal bagi saya.)
Saya memasukkan cache yang terlewat ketika muncul sebagai komentar pertama. Saya memeriksa semua kesalahan cache yang dapat diukur pada mesin saya perf
, bukan hanya yang diberikan di atas. Tembolok yang hilang sangat bising dan tidak menunjukkan korelasi dengan waktu eksekusi.