为什么仪器化C程序运行速度更快?

我正在研究(相当大的)现有的单线程C应用程序。 在这种情况下,我修改了应用程序来执行一些额外的工作,每次我们调用一个特殊的函数(这个函数被称为〜80.000次),递增一个计数器。 该应用程序在运行64位Linux内核3.2.0-31的Ubuntu 12.04上进行编译 – 与-O3选项一起使用。

令人惊讶的代码的仪表版本运行速度更快,我正在调查为什么。我用clock_gettime(CLOCK_PROCESS_CPUTIME_ID)来衡量执行时间,并获得代表性的结果,我报告平均执行时间值超过100次运行。 此外,为了避免来自外界的干扰,我尽可能地在没有任何其他应用程序运行的情况下尝试启动应用程序(在附注中,因为CLOCK_PROCESS_CPUTIME_ID返回处理时间而不是挂钟时间,所以其他应用程序“应该”在理论只影响caching而不直接影响stream程的执行时间)

我怀疑“指令caching效应”,也许有点大(less量字节)的检测代码适合不同和更好的caching,这个假设是可以想象的吗? 我试图用valegrind –tool = cachegrind做一些caching调查,但不幸的是,仪表版本(似乎是逻辑上的)比初始版本更多的caching未命中。

这个主题和想法的任何提示都可以帮助我们找出为什么仪表代码运行得更快,这是可以接受的(某些GCC优化可以在一种情况下使用,为什么?)

由于这个问题的细节并不多,所以在调查问题时,我只能提出一些考虑因素。

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

  1. 增加一个计数器可能会增加一些循环的大小,并防止循环展开。 这可能会减小代码的大小(并改善代码的局部性,这对于指令或微代码缓存或循环缓存是很好的,并允许CPU快速获取/解码指令)。
  2. 增加计数器可能会增加某些功能的大小并防止内联。 这也可能会减少代码的大小。
  3. 递增计数器可能会阻止自动矢量化,这又会减小代码的大小。

即使此更改不影响编译器优化,也可能会改变CPU如何执行代码的方式。

  1. 如果插入反递增的代码,分支目标充分,这可能会使分支目标密度减小,并改善分支预测。
  2. 如果在某个特定的分支目标前面插入反递增的代码,这可能会使分支目标的地址更好地对齐,并使代码提取更快。
  3. 如果在写入一些数据之后但在再次加载相同的数据(并且由于某种原因,存储 – 加载转发不工作)之前放置反递增的代码,则加载操作可以更早完成。
  4. 插入反递增的代码可能会阻止对L1数据高速缓存中的同一个存储块的两次冲突的加载尝试。
  5. 插入反递增的代码可能会改变某些CPU调度程序的决定,并使一些执行端口及时地提供给一些性能关键的指令。

为了调查编译器优化的效果,您可以比较在添加反递增代码之前和之后生成的汇编代码。

要调查CPU效应,请使用分析器来检查处理器性能计数器。

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