C语言中奇技淫巧04-仅对指定函数启用编译优化

发布于:2025-07-27 ⋅ 阅读:(14) ⋅ 点赞:(0)

相信很多人使用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 函数仍遵循全局 -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 值的乘法和累加,将循环吞吐量提升数倍。

__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指令进行优化:

//本函数使用默认优化级-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 // 恢复全局优化级别

注意事项:

  1. 不是 C 语言标准!!!C 语言标准(如 C99、C11、C17 等)仅定义了语言的语法、语义和标准库,未规定编译器优化相关的属性语法。attribute 关键字是 GCC(GNU Compiler Collection)为代表的编译器引入的 非标准扩展,用于向编译器传递额外信息(如优化策略、代码生成约束等)。
    主要由 GCC、Clang 等兼容 GCC 扩展的编译器支持,MSVC、ICC 等其他编译器可能不支持或使用不同语法(如 MSVC 使用 __declspec 或 #pragma)。若代码中使用此类扩展,可能导致在非 GCC 系编译器上编译失败,需通过条件编译(如 #ifdef GNUC)处理兼容性。
  2. 优化级别语法: 可指定具体级别(O0/O1/O2/O3/Os),或附加选项(如 optimize(“O2”, “unroll-loops”))。
  3. 与全局优化的关系: 函数属性优先级高于全局编译选项,但部分全局优化(如 -ffast-math)可能仍会影响函数。

网站公告

今日签到

点亮在社区的每一天
去签到