相信很多人使用GCC编译代码时,都会接触到gcc -O0/1/2/3/s,知道它可以对工程进行全局优化。
事实上,除了全局优化外,使用GCC扩展方式,我们还可以仅对部分关键函数实施差异化编译优化。
在GCC编译器中,attribute ((optimize("Ox"))) 可以为单个函数显式指定优化级别,覆盖全局编译选项(如 -O0 或 -Os)。这一特性适用于需要对特定函数进行针对性优化的场景(例如性能关键路径),而其他函数保持较低优化级别以便调试。
使用示例:
c
#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 函数仍遵循全局 -O0
b = 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 值的乘法和累加,将循环吞吐量提升数倍。
asm
__attribute__((optimize("O3")))
int foo(int a, int b) {
1130: f3 0f 1e fa endbr64
int i, j, v;
for (i = 0; i < a; i++) {
1134: 85 ff test %edi,%edi
1136: 0f 8e f3 00 00 00 jle 122f <foo+0xff>
113c: 41 89 f1 mov %esi,%r9d
113f: 41 89 f2 mov %esi,%r10d
1142: 44 8d 5e ff lea -0x1(%rsi),%r11d
1146: 31 c9 xor %ecx,%ecx
1148: 41 c1 e9 02 shr $0x2,%r9d
114c: 41 83 e2 fc and $0xfffffffc,%r10d
1150: 45 31 c0 xor %r8d,%r8d
for (j = 0; j < b; j++) {
1153: 85 f6 test %esi,%esi
1155: 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: 00
1163: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
1168: 41 83 fb 15 cmp $0x15,%r11d
116c: 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: 00
117f: 31 c0 xor %eax,%eax
1181: 66 0f ef d2 pxor %xmm2,%xmm2
1185: 66 0f 70 e7 00 pshufd $0x0,%xmm7,%xmm4
118a: 66 0f 6f ec movdqa %xmm4,%xmm5
118e: 66 0f 73 d5 20 psrlq $0x20,%xmm5
1193: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
1198: 66 0f 6f c3 movdqa %xmm3,%xmm0
119c: 83 c0 01 add $0x1,%eax
119f: 66 0f fe de paddd %xmm6,%xmm3
v += i*j;
11a3: 66 0f 6f c8 movdqa %xmm0,%xmm1
11a7: 66 0f 73 d0 20 psrlq $0x20,%xmm0
11ac: 66 0f f4 cc pmuludq %xmm4,%xmm1
11b0: 66 0f f4 c5 pmuludq %xmm5,%xmm0
11b4: 66 0f 70 c9 08 pshufd $0x8,%xmm1,%xmm1
11b9: 66 0f 70 c0 08 pshufd $0x8,%xmm0,%xmm0
11be: 66 0f 62 c8 punpckldq %xmm0,%xmm1
11c2: 66 0f fe d1 paddd %xmm1,%xmm2
for (j = 0; j < b; j++) {
11c6: 44 39 c8 cmp %r9d,%eax
11c9: 75 cd jne 1198 <foo+0x68>
11cb: 66 0f 6f c2 movdqa %xmm2,%xmm0
11cf: 66 0f 73 d8 08 psrldq $0x8,%xmm0
11d4: 66 0f fe d0 paddd %xmm0,%xmm2
11d8: 66 0f 6f c2 movdqa %xmm2,%xmm0
11dc: 66 0f 73 d8 04 psrldq $0x4,%xmm0
11e1: 66 0f fe d0 paddd %xmm0,%xmm2
11e5: 66 0f 7e d0 movd %xmm2,%eax
11e9: 01 c1 add %eax,%ecx
11eb: 44 89 d0 mov %r10d,%eax
11ee: 44 39 d6 cmp %r10d,%esi
11f1: 74 19 je 120c <foo+0xdc>
11f3: 89 c2 mov %eax,%edx
11f5: 41 0f af d0 imul %r8d,%edx
11f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1200: 83 c0 01 add $0x1,%eax
v += i*j;
1203: 01 d1 add %edx,%ecx
for (j = 0; j < b; j++) {
1205: 44 01 c2 add %r8d,%edx
1208: 39 f0 cmp %esi,%eax
120a: 7c f4 jl 1200 <foo+0xd0>
for (i = 0; i < a; i++) {
120c: 41 83 c0 01 add $0x1,%r8d
1210: 44 39 c7 cmp %r8d,%edi
1213: 0f 85 4f ff ff ff jne 1168 <foo+0x38>
}
}
return v;
}
1219: 89 c8 mov %ecx,%eax
121b: c3 ret
for (i = 0; i < a; i++) {
121c: 41 83 c0 01 add $0x1,%r8d
1220: 44 39 c7 cmp %r8d,%edi
1223: 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,%eax
122d: eb c4 jmp 11f3 <foo+0xc3>
for (i = 0; i < a; i++) {
122f: 31 c9 xor %ecx,%ecx
}
1231: 89 c8 mov %ecx,%eax
1233: c3 ret
通过 SIMD 指令并行处理数据、循环分块适配向量长度、寄存器深度复用以消除内存访问,最终实现执行速度的大幅提升。
而使用默认优化级别的foo2()
函数未对循环做任何展开,也未调用SIMD指令进行优化:
asm
//本函数使用默认优化级-O0,与foo()进行优化对比
int foo2(int a, int b) {
1234: f3 0f 1e fa endbr64
1238: 55 push %rbp
1239: 48 89 e5 mov %rsp,%rbp
123c: 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),%eax
1257: 0f af 45 f8 imul -0x8(%rbp),%eax
125b: 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),%eax
1265: 3b 45 e8 cmp -0x18(%rbp),%eax
1268: 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),%eax
1271: 3b 45 ec cmp -0x14(%rbp),%eax
1274: 7c d5 jl 124b <foo2+0x17>
}
}
return v;
1276: 8b 45 fc mov -0x4(%rbp),%eax
}
1279: 5d pop %rbp
127a: 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)可能仍会影响函数。