gcc -O3问题,从不同的文件调用相同的函数会产生不同的性能

mie*_*war 3 c optimization performance gcc

我正在运行以下基准测试:

int main(int argc, char **argv)
{
 char *d = malloc(sizeof(char) * 13);

 TIME_THIS(func_a(999, d), 99999999);
 TIME_THIS(func_b(999, d), 99999999);

 return 0;
}
Run Code Online (Sandbox Code Playgroud)

通过正常编译,两个函数的结果相同

% gcc func_overhead.c func_overhead_plus.c -o func_overhead && ./func_overhead                                                                               
[func_a(999, d)                     ]      9276227.73
[func_b(999, d)                     ]      9265085.90
Run Code Online (Sandbox Code Playgroud)

但是与-O3他们是非常不同的

% gcc -O3 func_overhead.c func_overhead_plus.c -o func_overhead && ./func_overhead                                                                
[func_a(999, d)                     ]    178580674.69
[func_b(999, d)                     ]     48450175.29
Run Code Online (Sandbox Code Playgroud)

func_a和func_b定义如下:

char *func_a(uint64_t id, char *d)
{
 register size_t i, j;
 register char c;

 for (i = 0, j = 36; i <= 11; i++)
  if (i == 4 || i == 8)
   d[i] = '/';
  else {
   c = ((id >> j) & 0xf) + '0';

   if (c > '9') 
    c = c - '9' - 1 + 'A';

   d[i] = c;

   j -= 4;
  }

 d[12] = '\0';

 return d;
}
Run Code Online (Sandbox Code Playgroud)

唯一的区别是与main()和func_b在同一文件中的func_a位于func_overhead_plus.c文件中

我想知道是否有人可以详细说明发生了什么

谢谢

编辑:

对于结果的所有混淆感到抱歉.它们实际上是每秒调用,因此func_a()比带有-O3的func_b()更快

TIME_THIS的定义如下:

double get_time(void)
{
    struct timeval t;
    gettimeofday(&t, NULL);
    return t.tv_sec + t.tv_usec*1e-6;
}

#define TIME_THIS(func, runs) do {                  \
        double t0, td;                              \
        int i;                                      \
        t0 = get_time();                            \
        for (i = 0; i < runs; i++)                  \
            func;                                   \
        td = get_time() - t0;                       \
        printf("[%-35s] %15.2f\n", #func, runs / td);   \
} while(0)
Run Code Online (Sandbox Code Playgroud)

该架构是Linux

Linux komiko 2.6.30-gentoo-r2 #1 SMP PREEMPT Wed Jul 15 17:27:51 IDT 2009 i686 Intel(R) Core(TM)2 Quad CPU Q8200 @ 2.33GHz GenuineIntel GNU/Linux
Run Code Online (Sandbox Code Playgroud)

gcc是4.3.3

如建议的那样,这里是混合调用的结果

-O3

[func_b(999, d)                     ]     48926120.09
[func_a(999, d)                     ]    135299870.52
[func_b(999, d)                     ]     49075900.30
[func_a(999, d)                     ]    135748939.12
[func_b(999, d)                     ]     49039535.67
[func_a(999, d)                     ]    134055084.58
Run Code Online (Sandbox Code Playgroud)

-02

[func_b(999, d)                     ]     27243732.97
[func_a(999, d)                     ]     27341371.38
[func_b(999, d)                     ]     27303284.93
[func_a(999, d)                     ]     27349177.65
[func_b(999, d)                     ]     27325398.25
[func_a(999, d)                     ]     27343935.88
Run Code Online (Sandbox Code Playgroud)

(-O1和-Os在本次测试中与-O2相同)

没有优化

[func_b(999, d)                     ]      8852314.88
[func_a(999, d)                     ]      9646166.81
[func_b(999, d)                     ]      8909973.33
[func_a(999, d)                     ]      9734883.99
[func_b(999, d)                     ]      8726127.49
[func_a(999, d)                     ]      9566052.21
Run Code Online (Sandbox Code Playgroud)

看起来没有优化行为像-O3那样func_a似乎比func_b更快

只是为了好玩,用gcc编译4.4.4似乎很有趣

没有优化

[func_b(999, d)                     ]     16982343.03
[func_a(999, d)                     ]     19693688.36
[func_b(999, d)                     ]     17260359.40
[func_a(999, d)                     ]     18137352.08
[func_b(999, d)                     ]     16790465.45
[func_a(999, d)                     ]     19828836.94
Run Code Online (Sandbox Code Playgroud)

-O3

[func_b(999, d)                     ]     52184739.72
[func_a(999, d)                     ] 99999237556468.61
[func_b(999, d)                     ]     52430823.56
[func_a(999, d)                     ]    101030101.92
[func_b(999, d)                     ]     52404446.52
[func_a(999, d)                     ]    100842538.40
Run Code Online (Sandbox Code Playgroud)

这很奇怪,不是吗?

编辑:

如果性能差异确实是gcc4.3/4.4无法跨对象内联,那么在同一个文件中包含性能关键函数是否应该被认为是一种好的做法?

例如

#include "performance_critical.c"
Run Code Online (Sandbox Code Playgroud)

或者它只是凌乱而且很可能不是很重要?

谢谢

Cog*_*eel 6

每当您对优化引擎下发生的事情感到好奇时,请查看-S选项.这将让您检查程序集输出,以确切了解两个版本之间的不同之处.

当编译器在单个文件(读取:转换单元)中工作时,它可以访问(预处理之后)内存在的所有类型,对象等.当另一个文件进入混合时,编译器不知道第一个文件中的代码.将两个目标文件放在一起的链接器只能看到符号名称和机器代码.

在您的情况下,编译器可能会弄清楚如何使用指针并实现它可以在第一个文件中内联函数调用.当您添加第二个文件时,它必须使用指针进行通信,因此您将获得添加的函数调用开销.

编辑

托拉克指出,我向后解释了这一点.我不知道为什么单文件版本的执行速度会更慢......

  • OP在哪里说func_a更慢?我看到-O3的"计时"数字增加,而func_a的数字最大.我的解释是,这些数字代表在特定时间内完成的工作量.所以我看到-O3 func_a运行速度最快 - 正如我应该根据Cogwheels解释我也订阅的那样. (2认同)