不合逻辑的基准测试?

时间:2009-03-23 00:03:45

标签: c optimization loops benchmarking

我目睹了以下奇怪的行为。我有两个函数,几乎相同 - 它们测量执行某个操作所需的循环次数。在一个函数中,在循环内部我增加一个变量;在另一个没有任何反应。变量是易变的,因此它们不会被优化掉。这些是功能:

unsigned int _osm_iterations=5000;

double osm_operation_time(){
    // volatile is used so that j will not be optimized, and ++ operation
    // will be done in each loop
    volatile unsigned int j=0;
    volatile unsigned int i;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
       ++j;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
         return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

double osm_empty_time(){
    volatile unsigned int i;
    volatile unsigned int j=0;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
        ;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
        return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

那里有一些非标准功能,但我相信你会管理。

问题是,第一个函数返回 4 ,而第二个函数(假设更少)返回 6 ,尽管第二个函数明显少于第一个函数之一。

这对任何人都有意义吗?

实际上我做了第一个函数,所以我可以减少测量第二个函数的循环开销。你知道怎么做吗(因为这种方法并没有真正削减它)?

我在使用Ubuntu(我认为是64位)。

非常感谢。

5 个答案:

答案 0 :(得分:4)

我在这里可以看到几件事。一个是两个循环的代码看起来相同。其次,编译器可能会意识到变量i和变量j将始终具有相同的值并优化其中一个。您应该查看生成的程序集,看看到底发生了什么。

另一种理论认为,对循环内部主体的更改已经影响了代码的可共享性 - 这可能会将其移动到缓存行或其他一些东西中。

由于代码非常简单,您可能会发现很难获得准确的计时值,即使您正在进行5000次迭代,您可能会发现时间在您正在使用的计时代码的误差范围内。一台现代计算机可能会在不到一毫秒的时间内运行 - 也许你应该增加迭代次数?

要在gcc中查看生成的程序集,specify the -S compiler option

  问:我怎么看一下汇编代码   由海湾合作委员会产生?

     

问:我怎样才能创建一个文件   查看C代码及其程序集   一起翻译?

     

答:使用-S(注意:大写S)开关   到GCC,它将发射组件   代码扩展名为.s的文件。   例如,以下命令:

     

gcc -O2 -S -c foo.c

     

将保留生成的汇编代码   在文件foo.s。

     

如果你想一起看C代码   随着组件被转换为,   使用这样的命令行:

     

gcc -c -g -Wa,-a,-ad [其他海湾合作委员会   选项] foo.c&gt; foo.lst

     

将输出组合   C /汇编列表到文件   foo.lst。

答案 1 :(得分:1)

这种事情在很大程度上取决于编译器优化和计时器分辨率。无论单位如何,您提供的结果(4和6)都很低。要正确地进行基准测试,您应该将这两个函数包装在一个执行它们几千次的循环中。

答案 2 :(得分:0)

有时很难猜测这种事情,特别是由于迭代次数较少。但是,可能发生的一件事是,增量可以在一个自由的整数执行单元上执行,获得一定程度的并行性,因为它没有对i的值进行删除。

由于您提到这是64位操作系统,因此几乎可以肯定所有这些值都在寄存器中,因为x86_64架构中有更多寄存器。除此之外,我会说执行更多迭代,看看结果有多稳定。

答案 3 :(得分:0)

如果您真的想要测试一段代码的操作(在这种情况下为"j++;"),那么您最好不要执行以下操作:

1 /在两个单独的可执行文件中执行它,因为可执行文件中的位置可能会影响代码。

2 /确保使用CPU时间而不是经过的时间(我不确定"tsc_readCycles_C()"给你的是什么)。这是为了避免CPU加载其他任务的错误结果。

3 /关闭编译器优化(例如,"gcc -O0")以确保gcc不会放入任何可能会使结果出现偏差的奇特内容。

4 /如果您使用实际结果,则无需担心volatile,例如放置:

printf ("%d\n",j);
循环后

或:

FILE *fx = fopen ("/dev/null","w");
fprintf (fx, "%d\n", j);
fclose (fx);

如果您根本不想要任何输出。我不记得volatile是编译器的建议还是强制执行。

5 / 5,000的迭代似乎有点偏低,“噪音”可能会影响读数。也许更高的价值会更好。如果您计算更大的代码并且刚刚将"j++;"作为占位符,那么这可能不是问题。

答案 4 :(得分:0)

当我运行与此类似的测试时,我通常会:

  1. 确保时间至少在几秒钟内测量,最好是(小)几十秒。
  2. 有一个程序运行调用第一个函数,然后是第二个函数,然后第一个函数再次调用,然后是第二个函数,依此类推,只是为了查看是否存在奇怪的缓存预热问题。
  3. 多次运行程序以查看运行时间的稳定程度。
  4. 我仍然无法解释你的观察结果,但是如果你确定你已经正确识别了你的功能(不明显的情况是因为早先有拷贝''''''''''例如),然后查看汇编器输出是剩下的主要选项。