-1

我目睹了以下奇怪的行为。我有两个功能几乎相同 - 它们测量执行某个操作所需的周期数。在一个函数中,在循环内我增加了一个变量;在其他什么都没有发生。这些变量是易变的,因此它们不会被优化掉。这些是功能:

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 位)。

非常感谢。

4

4 回答 4

4

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

另一种理论是,对循环内部主体的更改影响了代码的可缓存性——这可能会将其移动到缓存行或其他一些东西上。

由于代码如此琐碎,您可能会发现很难获得准确的时序值,即使您进行 5000 次迭代,您可能会发现时间在您使用的时序代码的误差范围内。现代计算机可能可以在不到一毫秒的时间内运行它——也许你应该增加迭代次数?

要在 gcc 中查看生成的程序集,请指定 -S 编译器选项

问:如何查看 GCC 生成的汇编代码?

问:如何创建一个可以同时查看 C 代码及其汇编翻译的文件?

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

gcc -O2 -S -c foo.c

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

如果您想查看 C 代码及其转换为的程序集,请使用如下命令行:

gcc -c -g -Wa,-a,-ad [其他 GCC 选项] foo.c > foo.lst

这会将组合的 C/程序集列表输出到文件 foo.lst。

于 2009-03-23T00:11:47.973 回答
0

有时很难猜测这种事情,尤其是由于迭代次数很少。但是,可能发生的一件事是增量可以在一个空闲的整数执行单元上执行,从而获得一些轻微的并行度,因为它与 i 的值没有关系。

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

于 2009-03-23T01:25:35.753 回答
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++;"作为占位符包含在内,这可能不是问题。

于 2009-03-23T01:27:03.347 回答
0

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

  1. 确保时间至少以秒计,最好是(小)几十秒。
  2. 让程序运行一次,调用第一个函数,然后调用第二个函数,然后再调用第一个函数,然后再调用第二个函数,以此类推,看看是否有奇怪的缓存预热问题。
  3. 多次运行程序以查看运行时间的稳定性。

我仍然无法解释您观察到的结果,但是如果您确定您的功能已正确识别(例如,鉴于之前存在复制粘贴错误,情况并非不言而喻),然后查看汇编器输出是剩下的主要选项。

于 2009-03-23T06:02:37.053 回答