相信很多人使用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)可能仍会影响函数。