为什么仪表化的C程序运行得更快?

时间:2012-10-01 09:30:32

标签: c linux performance gcc

我正在研究一个(非常大的)现有的单线程C应用程序。在这种情况下,我修改了应用程序以执行一些非常少的额外工作,包括每次调用特殊函数时递增计数器(此函数被称为~80.000次)。该应用程序在运行64位Linux内核3.2.0-31-generic和-O3选项的Ubuntu 12.04上编译。

令人惊讶的是,代码的检测版本运行得更快,我正在调查原因。我使用clock_gettime(CLOCK_PROCESS_CPUTIME_ID)测量执行时间并获得代表性结果,我报告的平均执行时间值超过100次运行。此外,为了避免来自外界的干扰,我尽可能地尝试在没有任何其他应用程序运行的系统中启动应用程序(在旁注中,因为CLOCK_PROCESS_CPUTIME_ID返回处理时间而不是挂钟时间,其他应用程序"应该"理论上只影响缓存而不是直接影响流程执行时间)

我怀疑"指令缓存效果",也许在缓存中更大(更少字节)适合不同且更好的检测代码是否可以想象?我尝试使用valegrind --tool = cachegrind进行一些缓存调查,但遗憾的是,已检测的版本(初看起来似乎合乎逻辑)比初始版本更多的缓存未命中。

有关此主题的任何提示以及可能有助于找到为什么检测代码运行得更快的想法都是受欢迎的(在一种情况下可以使用一些GCC优化而在另一种情况下没有,为什么?,...)

2 个答案:

答案 0 :(得分:4)

由于问题中的细节不多,我只能在调查问题时建议考虑一些因素。

很少有额外的工作(例如递增计数器)可能会改变编译器是否应用某些优化的决定。编译器并不总是有足够的信息来做出完美的选择。它可能会尝试针对瓶颈是代码大小的速度进行优化。当没有太多要处理的数据时,它可能会尝试自动矢量化计算。编译器可能不知道要处理什么类型的数据或者将执行代码的CPU的确切模型是什么。

  1. 增加计数器可能会增加某些循环的大小并阻止循环展开。这可能会减少代码大小(并改善代码局部性,这对指令或微代码缓存或循环缓冲区很有用,并允许CPU快速获取/解码指令。)
  2. 增加计数器可能会增加某些功能的大小并阻止内联。这也可能会减少代码大小。
  3. 递增计数器可能会阻止自动矢量化,这可能会减少代码大小。
  4. 即使此更改不会影响编译器优化,也可能会改变CPU执行代码的方式。

    1. 如果您插入反向递增代码,充满分支目标,这可能会使分支目标密度降低并改善分支预测。
    2. 如果在某个特定分支目标前插入反递增代码,这可能会使分支目标的地址更好地对齐,并使代码获取更快。
    3. 如果在写入某些数据之后但在再次加载相同数据之前放置反向递增代码(并且存储到加载转发由于某种原因不起作用),则可以提前完成加载操作。
    4. 插入反递增代码可能会阻止对L1数据缓存中同一个存储区的两次冲突加载尝试。
    5. 插入反递增代码可能会改变某些CPU调度程序的决定,并使某些执行端口及时可用于某些性能关键指令。
    6. 要研究编译器优化的效果,可以在添加反递增代码之前和之后比较生成的汇编代码。

      要调查CPU效果,请使用允许检查处理器性能计数器的分析器。

答案 1 :(得分:1)

根据我对嵌入式编译器的经验,编译器中的优化工具会寻找递归任务。也许额外的代码迫使编译器看到更加递归的东西,并且它以不同的方式构造机器代码。编译器为优化做了一些奇怪的事情。在某些语言中(我认为Perl?)“非not”条件的执行速度比“true”条件更快。您的调试工具是否允许您单步执行代码/程序集比较?这可以增加一些关于编译器决定如何处理额外任务的见解。