Lần đầu tiên tôi nhận thấy vào năm 2009 rằng GCC (ít nhất là trong các dự án và trên máy của tôi) có xu hướng tạo mã nhanh hơn đáng kể nếu tôi tối ưu hóa kích thước ( -Os
) thay vì tốc độ ( -O2
hoặc -O3
), và tôi đã tự hỏi tại sao.
Tôi đã quản lý để tạo mã (khá ngớ ngẩn) cho thấy hành vi đáng ngạc nhiên này và đủ nhỏ để được đăng ở đây.
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;
}
Nếu tôi biên dịch nó với -Os
, phải mất 0,38 giây để thực hiện chương trình này và 0,44 giây nếu nó được biên dịch bằng -O2
hoặc -O3
. Những thời gian này thu được một cách nhất quán và thực tế không có tiếng ồn (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M).
(Cập nhật: Tôi đã chuyển tất cả mã lắp ráp sang GitHub : Họ đã làm cho bài đăng trở nên cồng kềnh và rõ ràng thêm rất ít giá trị cho các câu hỏi vì các fno-align-*
cờ có cùng tác dụng.)
Đây là lắp ráp được tạo ra với -Os
và -O2
.
Thật không may, sự hiểu biết của tôi về lắp ráp rất hạn chế, vì vậy tôi không biết liệu những gì tôi làm tiếp theo có đúng hay không: Tôi đã nắm lấy lắp ráp -O2
và hợp nhất tất cả sự khác biệt của nó vào lắp ráp -Os
ngoại trừ các .p2align
dòng, kết quả ở đây . Mã này vẫn chạy trong 0,38 giây và sự khác biệt duy nhất là .p2align
công cụ.
Nếu tôi đoán chính xác, đây là những miếng đệm để căn chỉnh ngăn xếp. Theo lý do tại sao GCC pad hoạt động với NOP? nó được thực hiện với hy vọng rằng mã sẽ chạy nhanh hơn, nhưng rõ ràng việc tối ưu hóa này đã phản tác dụng trong trường hợp của tôi.
Có phải đó là phần đệm là thủ phạm trong trường hợp này? Lý do tại sao và làm thế nào?
Tiếng ồn mà nó tạo ra khá nhiều làm cho việc tối ưu hóa vi thời gian là không thể.
Làm cách nào tôi có thể chắc chắn rằng sự sắp xếp may mắn / không may mắn như vậy không can thiệp khi tôi thực hiện tối ưu hóa vi mô (không liên quan đến căn chỉnh ngăn xếp) trên mã nguồn C hoặc C ++?
CẬP NHẬT:
Theo câu trả lời của Pascal Cuoq, tôi đã sửa lại một chút với sự sắp xếp. Bằng cách chuyển -O2 -fno-align-functions -fno-align-loops
đến gcc, tất cả .p2align
đã biến mất khỏi hội đồng và thực thi được tạo trong 0,38 giây. Theo tài liệu gcc :
-Os cho phép tối ưu hóa tất cả -O2 [nhưng] -Os vô hiệu hóa các cờ tối ưu hóa sau:
-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays
Vì vậy, nó có vẻ như là một vấn đề liên kết (mis).
Tôi vẫn còn hoài nghi về -march=native
đề xuất trong câu trả lời của Marat Dukhan . Tôi không tin rằng nó không chỉ can thiệp vào vấn đề căn chỉnh (mis) này; Nó hoàn toàn không có tác dụng với máy của tôi. (Tuy nhiên, tôi đã nêu lên câu trả lời của anh ấy.)
CẬP NHẬT 2:
Chúng ta có thể lấy -Os
ra khỏi hình ảnh. Các lần sau có được bằng cách biên dịch với
-O2 -fno-omit-frame-pointer
0,37s-O2 -fno-align-functions -fno-align-loops
0,37s-S -O2
sau đó tự di chuyển lắp rápadd()
sauwork()
0,37s-O2
0,44s
Dường như với tôi khoảng cách add()
từ trang web cuộc gọi rất nhiều vấn đề. Tôi đã thử perf
, nhưng đầu ra perf stat
và perf report
rất ít ý nghĩa đối với tôi. Tuy nhiên, tôi chỉ có thể nhận được một kết quả nhất quán từ nó:
-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
Dành cho 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
¦ }
Dành cho -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
Có vẻ như chúng tôi đang trì hoãn cuộc gọi đến add()
trong trường hợp chậm.
Tôi đã kiểm tra tất cả mọi thứ mà perf -e
có thể nhổ ra trên máy tính của tôi; không chỉ các số liệu thống kê được đưa ra ở trên.
Đối với cùng thực thi, stalled-cycles-frontend
hiển thị tương quan tuyến tính với thời gian thực hiện; Tôi đã không nhận thấy bất cứ điều gì khác sẽ tương quan rõ ràng như vậy. (So sánh stalled-cycles-frontend
với các thực thi khác nhau không có ý nghĩa với tôi.)
Tôi đã bao gồm các lỗi nhớ cache khi nó xuất hiện như là bình luận đầu tiên. Tôi đã kiểm tra tất cả các lỗi bộ nhớ cache có thể đo được trên máy của mình perf
, không chỉ các lỗi được nêu ở trên. Các lỗi bộ nhớ cache rất rất ồn ào và hiển thị rất ít hoặc không tương quan với thời gian thực hiện.