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

时间:2010-07-06 17:24:33

标签: c performance optimization 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;
}

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

% 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

但是-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

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;
}

唯一的区别是与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)

架构是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

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

-O2

[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

( - 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

看起来没有优化行为像-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

-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

这很奇怪,不是吗?

修改

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

例如

#include "performance_critical.c"

还是只是凌乱而且很可能不是很重要?

由于

6 个答案:

答案 0 :(得分:6)

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

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

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

修改

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

答案 1 :(得分:1)

这都是关于代码缓存的。 这里最重要的功能是TIME_THIS,在您的描述中缺少。 我用TIME_THIS_A和TIME_THIS_B重写了你的测试,它们位于相应的c文件中。 然后随着任何优化,效果消失,两者都具有相同的速度。 如果TIME_THIS位于main.c中,我可以看到func_b更快的效果。 但正如我所说,这就是指令缓存。 甚至通过一些愚蠢的方法扩大第二个c文件(我使用main并将其重命名为main_b)有一些影响。 如果您启用L1 / L2缓存未命中日志记录,您可以看到发生这种情况的原因和位置。 为了完整起见,我的(几乎)完整的代码在这里:

// main.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "foo_b.h"

void prefoo_a()
{
    static volatile int i = 1;
}

void foo_a()
{
    char *d = malloc(sizeof(char) * 13);

    for ( int i = 0; i < 10000000; i++ )
        func_a(999, d);
}

char *func_a(uint64_t id, char *d)
{
    // snipped

    return d;
}

int main(int argc, char **argv)
{   
    clock_t start;

    prefoo_a();
    start = clock();
    foo_a();
    printf ( "func_a %f\n", ( (double)clock() - start ) / CLOCKS_PER_SEC );

    prefoo_b();
    start = clock();
    foo_b();
    printf ( "func_b %f\n", ( (double)clock() - start ) / CLOCKS_PER_SEC );

    prefoo_a();
    start = clock();
    foo_a();
    printf ( "func_a %f\n", ( (double)clock() - start ) / CLOCKS_PER_SEC );

    prefoo_b();
    start = clock();
    foo_b();
    printf ( "func_b %f\n", ( (double)clock() - start ) / CLOCKS_PER_SEC );

    return 0;
}

和foo.b

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "foo_b.h"

void prefoo_b()
{
    static volatile int i = 1;
}

void foo_b()
{
    char *d = malloc(sizeof(char) * 13);

    for ( int i = 0; i < 10000000; i++ )
        func_b(999, d);
}

char *func_b(uint64_t id, char *d)
{
            // ... snippped

    return d;
}

一个合适的标题:

typedef unsigned long long uint64_t;
char *func_a(uint64_t id, char *d);
char *func_b(uint64_t id, char *d);
void prefoo_a();
void prefoo_b();
void foo_a();
void foo_b();

我的计算机(x64 Snow Leopard)上的结果-O3:

  • func_a 0.043674
  • func_b 0.043825
  • func_a 0.044268
  • func_b 0.043997
  • func_a 0.043879
  • func_b 0.043950

与-Od相同

  • func_a 0.853132
  • func_b 0.852719
  • func_a 0.872263
  • func_b 0.851980
  • func_a 0.852977
  • func_b 0.853294

答案 2 :(得分:1)

我的猜测 - 内联。

-O3执行积极的内联,但-O2不执行。可以在同一个文件中内联,直到gcc 4.5才能实现跨对象的内联。

答案 3 :(得分:0)

建议不要使用

-O3。请参阅Optimization in GCC

答案 4 :(得分:0)

你没有告诉我们很多关于你运行这个例子的平台,但是在任何情况下,当你使用优化标志时这种戏剧性的减速看起来很奇怪。您可能有测量问题,但我们也只能推测,因为您没有向我们展示您的TIME_THIS宏。

在任何情况下,您提供的这些测量结果都不是非常确定的,因为如果您只运行一次函数并且始终以相同的顺序运行,则可能会产生各种副作用。

要得出结论,你应该在没有测量的情况下运行几次函数,然后以随机顺序循环,累计时间并在结束时求平均值。

是的,正如其他人所说,首先看看汇编程序,看看是否有一些奇怪之处。

答案 5 :(得分:0)

我认为用

移动头文件中的整个函数
static inline

将解决问题。编译器通常无法跨模块进行优化。