删除代码部分与分析器的数据不匹配

时间:2014-12-02 20:11:57

标签: c++ optimization profiler

我正在做一些概念概要证明并优化示例类型。但是,我遇到了一些我无法解释的事情,我希望有人可以解决这个问题。

我写了一段很短的代码:

int main (void)
{
    for (int j = 0; j < 1000; j++)
    {
        a = 1;
        b = 2;
        c = 3;

        for (int i = 0; i < 100000; i++)
        {
            callbackWasterOne();
            callbackWasterTwo();
        }
        printf("Two a: %d, b: %d, c: %d.", a, b, c);
    }
    return 0;
}

void callbackWasterOne(void)
{
    a = b * c;
}
void callbackWasterTwo(void)
{
    b = a * c;
}

所有这一切都是调用两个非常基本的函数,只是将数字相乘。由于代码是相同的,我希望探查器(oprofile)返回大致相同的数字。

我为每个配置文件运行此代码10次,我得到以下值,表示每个函数花费的时间:

  • main:average = 5.60%,stdev = 0.10%
  • callbackWasterOne = 43.78%,stdev = 1.04%
  • callbackWasterTwo = 50.24%,stdev = 0.98%
  • 休息是像printf和no-vmlinux
  • 这样的杂项

callbackWasterOne和callbackWasterTwo之间的时间差异非常大(至少对我而言),因为它们具有相同的代码,我在代码中切换了它们的顺序,并且现在用以下结果重新启动了分析器:

  • main:平均值= 5.45%,stdev = 0.40%
  • callbackWasterOne = 50.69%,stdev = 0.49%
  • callbackWasterTwo = 43.54%,stdev = 0.18%
  • 休息是像printf和no-vmlinux
  • 这样的杂项

显然,探查器根据执行顺序采样比另一个更多。不好。无视这一点,我决定看到删除一些代码的效果,我得到了执行时间(平均值):

  • 没有删除:0.5295s
  • 调用callbackWasterOne()从for循环中删除:0.2075s
  • 调用callbackWasterTwo()从for循环中删除:0.2042s
  • 从for循环删除两个调用:0.1903s
  • 删除两个调用和for循环:0.0025s
  • 删除callbackWasterOne的内容:0.379s
  • 删除callbackWasterTwo的内容:0.378s
  • 删除两者的内容:0.382s

所以这就是我理解的问题:

  • 当我从for循环中删除其中一个调用时,执行时间下降了大约60%,这大大超过了那个函数+ main在第一个位置所花费的时间!这怎么可能?
  • 与仅删除一个调用相比,为什么从循环中删除两个调用的效果如此之小?我无法弄清楚这种非线性。我知道for循环很昂贵,但在这种情况下(如果大部分剩余时间可以归因于执行函数调用的for循环),为什么删除其中一个调用会导致第一个地方出现如此大的改进?

我查看了反汇编,代码中的两个函数是相同的。对它们的呼叫是相同的,删除呼叫只会删除一个呼叫线路。

可能相关的其他信息

  • 我使用的是Ubuntu 14.04LTS
  • Eclipse编写的代码没有优化(O0)
  • 我使用&#34; time&#34;
  • 在终端中运行代码
  • 我使用计数= 10000和10次重复的OProfile。

以下是我使用-O1优化时的结果:

  • main:avg = 5.89%,stdev = 0.14%
  • callbackWasterOne:avg = 44.28%,stdev = 2.64%
  • callbackWasterTwo:avg = 49.66%,stdev = 2.54%(比以前更大)
  • 休息是其他的

删除各种位的结果(执行时间平均值):

  • 没有删除:0.522s
  • 删除callbackWasterOne调用:0.149s(减少71.47%)
  • 删除callbackWasterTwo调用:0.123%(减少76.45%)
  • 删除两个电话:0.0365s(减少93.01%)(根据上面的个人资料数据我的期望)

因此现在删除一个调用比以前好多了,删除它们仍然带来好处(可能是因为优化器理解循环中没有任何反应)。尽管如此,删除一个比我预期的更有益。

使用不同变量的两个函数的结果: 我为callbackWasterTwo()定义了另外3个变量,而不是重用相同的变量。现在结果是我所期望的。

  • main:avg = 10.87%,stdev = 0.19%(平均值更大,但可能是由于这些新变量)
  • callbackWasterOne:avg = 46.08%,stdev = 0.53%
  • callbackWasterTwo:avg = 42.82%,stdev = 0.62%
  • 休息是其他的

删除各种位的结果(执行时间平均值):

  • 没有删除:0.520s
  • 删除callbackWasterOne调用:0.292s(减少43.83%)
  • 删除callbackWasterTwo调用:0.291%(减少44.07%)
  • 删除两个电话:0.065秒(减少87.55%)

所以现在删除两个调用与删除一个调用+另一个调用几乎相同(在stdev中)。 由于删除任一函数的结果几乎相同(43.83%对比44.07%),我将说明问题可能是剖析器数据(46%对42%)仍然存在偏差。也许这就是它的样本(接下来要改变计数器值,看看会发生什么)。

似乎优化的成功与代码重用分数有很大关系。实现&#34;完全&#34;的唯一方法(你知道我的意思)探查器指出的加速是优化完全独立的代码。无论如何,这一切都很有趣。

我仍然在寻找-O1案例减少70%的一些解释提示......

我用10个函数(每个函数中有不同的公式,但是使用6个不同变量的一些组合,一次3个,所有乘法)来做到这一点:

至少可以说这些结果令人失望。我知道功能是相同的,但是,分析器表明有些功能需要更长的时间。无论我删除哪一个(&#34;快速&#34;或&#34;慢&#34;一个),结果都是一样的;)所以这让我想知道,有多少人错误地依赖于探查器指出要修复的错误代码区域?如果我在不知不觉中看到了这些结果,有什么可能告诉我去修复5%的功能而不是20%(即使它们完全一样)?如果5%的一个更容易修复,具有很大的潜在利益怎么办?当然,这个分析器可能不是很好,但它很受欢迎!人们用它!

这是截图。我不想再次输入它: Opcontrol

我的结论:我对Oprofile感到非常失望。我决定通过命令行在同一个函数上尝试callgrind(valgrind),它给了我更多合理的结果。实际上,结果非常合理(所有函数花费的时间相同 - 执行时间相同)。我认为Callgrind的样本远远超过Oprofile。

Callgrind仍然无法解释删除函数时改进的差异,但至少它提供了正确的基线信息......

1 个答案:

答案 0 :(得分:2)

啊,我看到你确实看过集会了。这个问题本身确实很有意思,但总的来说,对未经优化的代码进行分析是没有意义的,因为即使在-O1中也可以很容易地减少这样的样板。

如果它真的只是缺少的调用,那么这可以解释时序差异 - -O0堆栈操作代码中有很多样板(任何调用者保存的寄存器都必须被压入堆栈,并且任何参数也是如此,然后必须处理任何返回值并且必须完成相反的堆栈操作)这有助于调用函数所花费的时间,但不一定完全归因于函数本身{{1因为该代码是在实际调用函数之前/之后执行的。

我怀疑第二个函数似乎总是花费更少时间的原因是需要完成更少(或没有)堆栈杂耍 - 由于之前的函数调用,参数值已经在堆栈中,所以,正如您所见,只需要执行对函数的调用,而无需任何其他额外工作。