相信很多人使用GCC編譯代碼時,都會接觸到gcc -O0/1/2/3/s,知道它可以對工程進行全局優化。
事實上,除了全局優化外,使用GCC擴展方式,我們還可以僅對部分關鍵函數實施差異化編譯優化。
在GCC編譯器中,attribute((optimize(“Ox”))) 可以為單個函數顯式指定優化級別,覆蓋全局編譯選項(如 -O0 或 -Os)。這一特性適用于需要對特定函數進行針對性優化的場景(例如性能關鍵路徑),而其他函數保持較低優化級別以便調試。
使用示例:
#include <stdio.h>// 全局編譯級別為 -O0(默認不優化)
// 但對 foo 函數單獨啟用 O3 優化
__attribute__((optimize("O3")))
int foo(int a, int b) {int i, j, v;for (i = 0; i < a; i++) {for (j = 0; j < b; j++) {v += i*j; }}return v;
}//本函數使用默認優化級-O0,與foo()進行優化對比
int foo2(int a, int b) {int i, j, v;for (i = 0; i < a; i++) {for (j = 0; j < b; j++) {v += i*j; }}return v;
}int main(void) {int a = 0, b = 0;a = foo(10, 2); // main 函數仍遵循全局 -O0b = foo(10, 2);return 0;
}
使用gcc test.c -g
進行編譯,并使用objdump -dS a.out
進行反匯編,可以看出foo()
與foo2()
函數匯編代碼大不相同。
其中,foo()
由于函數包含 計算密集型嵌套循環(v += i*j),-O3 觸發了 自動向量化,通過 128位 SSE2 指令(如 pmuludq、paddd)并行處理多個 j 值的乘法和累加,將循環吞吐量提升數倍。
__attribute__((optimize("O3")))
int foo(int a, int b) {1130: f3 0f 1e fa endbr64int i, j, v;for (i = 0; i < a; i++) {1134: 85 ff test %edi,%edi1136: 0f 8e f3 00 00 00 jle 122f <foo+0xff>113c: 41 89 f1 mov %esi,%r9d113f: 41 89 f2 mov %esi,%r10d1142: 44 8d 5e ff lea -0x1(%rsi),%r11d1146: 31 c9 xor %ecx,%ecx1148: 41 c1 e9 02 shr $0x2,%r9d114c: 41 83 e2 fc and $0xfffffffc,%r10d1150: 45 31 c0 xor %r8d,%r8dfor (j = 0; j < b; j++) {1153: 85 f6 test %esi,%esi1155: 0f 8e c1 00 00 00 jle 121c <foo+0xec>115b: 66 0f 6f 35 bd 0e 00 movdqa 0xebd(%rip),%xmm6 # 2020 <_IO_stdin_used+0x20>1162: 001163: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)1168: 41 83 fb 15 cmp $0x15,%r11d116c: 0f 86 b9 00 00 00 jbe 122b <foo+0xfb>1172: 66 41 0f 6e f8 movd %r8d,%xmm7
int foo(int a, int b) {1177: 66 0f 6f 1d 91 0e 00 movdqa 0xe91(%rip),%xmm3 # 2010 <_IO_stdin_used+0x10>117e: 00117f: 31 c0 xor %eax,%eax1181: 66 0f ef d2 pxor %xmm2,%xmm21185: 66 0f 70 e7 00 pshufd $0x0,%xmm7,%xmm4118a: 66 0f 6f ec movdqa %xmm4,%xmm5118e: 66 0f 73 d5 20 psrlq $0x20,%xmm51193: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)1198: 66 0f 6f c3 movdqa %xmm3,%xmm0119c: 83 c0 01 add $0x1,%eax119f: 66 0f fe de paddd %xmm6,%xmm3v += i*j;11a3: 66 0f 6f c8 movdqa %xmm0,%xmm111a7: 66 0f 73 d0 20 psrlq $0x20,%xmm011ac: 66 0f f4 cc pmuludq %xmm4,%xmm111b0: 66 0f f4 c5 pmuludq %xmm5,%xmm011b4: 66 0f 70 c9 08 pshufd $0x8,%xmm1,%xmm111b9: 66 0f 70 c0 08 pshufd $0x8,%xmm0,%xmm011be: 66 0f 62 c8 punpckldq %xmm0,%xmm111c2: 66 0f fe d1 paddd %xmm1,%xmm2for (j = 0; j < b; j++) {11c6: 44 39 c8 cmp %r9d,%eax11c9: 75 cd jne 1198 <foo+0x68>11cb: 66 0f 6f c2 movdqa %xmm2,%xmm011cf: 66 0f 73 d8 08 psrldq $0x8,%xmm011d4: 66 0f fe d0 paddd %xmm0,%xmm211d8: 66 0f 6f c2 movdqa %xmm2,%xmm011dc: 66 0f 73 d8 04 psrldq $0x4,%xmm011e1: 66 0f fe d0 paddd %xmm0,%xmm211e5: 66 0f 7e d0 movd %xmm2,%eax11e9: 01 c1 add %eax,%ecx11eb: 44 89 d0 mov %r10d,%eax11ee: 44 39 d6 cmp %r10d,%esi11f1: 74 19 je 120c <foo+0xdc>11f3: 89 c2 mov %eax,%edx11f5: 41 0f af d0 imul %r8d,%edx11f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)1200: 83 c0 01 add $0x1,%eaxv += i*j;1203: 01 d1 add %edx,%ecxfor (j = 0; j < b; j++) {1205: 44 01 c2 add %r8d,%edx1208: 39 f0 cmp %esi,%eax120a: 7c f4 jl 1200 <foo+0xd0>for (i = 0; i < a; i++) {120c: 41 83 c0 01 add $0x1,%r8d1210: 44 39 c7 cmp %r8d,%edi1213: 0f 85 4f ff ff ff jne 1168 <foo+0x38>}}return v;
}1219: 89 c8 mov %ecx,%eax121b: c3 retfor (i = 0; i < a; i++) {121c: 41 83 c0 01 add $0x1,%r8d1220: 44 39 c7 cmp %r8d,%edi1223: 0f 85 2a ff ff ff jne 1153 <foo+0x23>1229: eb ee jmp 1219 <foo+0xe9>for (j = 0; j < b; j++) {122b: 31 c0 xor %eax,%eax122d: eb c4 jmp 11f3 <foo+0xc3>for (i = 0; i < a; i++) {122f: 31 c9 xor %ecx,%ecx
}1231: 89 c8 mov %ecx,%eax1233: c3 ret
通過 SIMD 指令并行處理數據、循環分塊適配向量長度、寄存器深度復用以消除內存訪問,最終實現執行速度的大幅提升。
而使用默認優化級別的foo2()
函數未對循環做任何展開,也未調用SIMD指令進行優化:
//本函數使用默認優化級-O0,與foo()進行優化對比
int foo2(int a, int b) {1234: f3 0f 1e fa endbr641238: 55 push %rbp1239: 48 89 e5 mov %rsp,%rbp123c: 89 7d ec mov %edi,-0x14(%rbp)123f: 89 75 e8 mov %esi,-0x18(%rbp)int i, j, v;for (i = 0; i < a; i++) {1242: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%rbp)1249: eb 23 jmp 126e <foo2+0x3a>for (j = 0; j < b; j++) {124b: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)1252: eb 0e jmp 1262 <foo2+0x2e>v += i*j;1254: 8b 45 f4 mov -0xc(%rbp),%eax1257: 0f af 45 f8 imul -0x8(%rbp),%eax125b: 01 45 fc add %eax,-0x4(%rbp)for (j = 0; j < b; j++) {125e: 83 45 f8 01 addl $0x1,-0x8(%rbp)1262: 8b 45 f8 mov -0x8(%rbp),%eax1265: 3b 45 e8 cmp -0x18(%rbp),%eax1268: 7c ea jl 1254 <foo2+0x20>for (i = 0; i < a; i++) {126a: 83 45 f4 01 addl $0x1,-0xc(%rbp)126e: 8b 45 f4 mov -0xc(%rbp),%eax1271: 3b 45 ec cmp -0x14(%rbp),%eax1274: 7c d5 jl 124b <foo2+0x17>}}return v;1276: 8b 45 fc mov -0x4(%rbp),%eax
}1279: 5d pop %rbp127a: c3 ret
如果需要對多個函數應用相同優化,也可使用 #pragma GCC optimize 作用于代碼塊:
#pragma GCC push_options
#pragma GCC optimize("O2")int bar(int x) { /* O2 優化 */ }
int baz(int y) { /* O2 優化 */ }#pragma GCC pop_options // 恢復全局優化級別
注意事項:
- 不是 C 語言標準!!!C 語言標準(如 C99、C11、C17 等)僅定義了語言的語法、語義和標準庫,未規定編譯器優化相關的屬性語法。attribute 關鍵字是 GCC(GNU Compiler Collection)為代表的編譯器引入的 非標準擴展,用于向編譯器傳遞額外信息(如優化策略、代碼生成約束等)。
主要由 GCC、Clang 等兼容 GCC 擴展的編譯器支持,MSVC、ICC 等其他編譯器可能不支持或使用不同語法(如 MSVC 使用 __declspec 或 #pragma)。若代碼中使用此類擴展,可能導致在非 GCC 系編譯器上編譯失敗,需通過條件編譯(如 #ifdef GNUC)處理兼容性。 - 優化級別語法: 可指定具體級別(O0/O1/O2/O3/Os),或附加選項(如 optimize(“O2”, “unroll-loops”))。
- 與全局優化的關系: 函數屬性優先級高于全局編譯選項,但部分全局優化(如 -ffast-math)可能仍會影響函數。