代码之家  ›  专栏  ›  技术社区  ›  amit kumar

现代CPU内环间接优化

  •  7
  • amit kumar  · 技术社区  · 14 年前

    http://www.boost.org/community/implementation_variations.html

    "... 编码差异(例如将类从虚拟成员更改为非虚拟成员或删除间接级别)不太可能产生任何可测量的差异,除非深入到内部循环中。即使在一个内部循环中,现代的CPU也经常以相同的时钟周期执行这样的竞争代码序列!”

    我试图理解“即使在内部循环”部分。具体来说,CPU实现了什么机制来在相同的时钟周期内执行这两个代码(虚拟代码与非虚拟代码或附加的间接寻址级别)?我知道指令流水线和缓存,但是如何在与非虚拟调用相同的时钟周期内执行虚拟调用呢?间接性是如何“丢失”的?

    5 回复  |  直到 14 年前
        1
  •  4
  •   Community Egal    7 年前

    缓存(例如。 branch target caching out-of-order execution 可能有助于改变 load - - branch 分支 . 指令折叠/消除(什么是合适的术语?)在解码或分支预测阶段的流水线也可能有贡献。

    不过,所有这些都依赖于很多不同的因素:有多少不同的分支目标(例如,您可能触发多少不同的虚拟重载)、您循环了多少事情(分支目标缓存“热”了吗?icache/dcache怎么样?),虚拟表或间接表在内存中的布局方式(它们是缓存友好型的,还是每个新的vtable加载都可能逐出一个旧的vtable?),是否由于多核乒乓等原因导致缓存重复失效。。。

    (免责声明:我绝对不是这里的专家,我的很多知识都来自于为了嵌入式处理器而学习,所以其中一些是外推。如果您有任何更正,请随时发表评论!)

    确定某个特定程序是否会出现问题的正确方法当然是分析。如果可以的话,可以借助硬件计数器来实现这一点——它们可以告诉您很多关于流水线各个阶段的情况。


    编辑:

    正如汉斯·帕桑在上述评论中指出的那样 Modern CPU Inner Loop Indirection Optimizations superscalar design 可能更重要(hit-under-miss是一个非常小的具体示例,完全冗余的负载单元可能更好)。

    branch dest
    

    …间接分支是三个(也许你可以在两个分支中得到它,但它大于一个):

    load vtable from this
    load dest from vtable
    branch dest
    

    这个 理论最小值 因此,第一个例子的时间是1个周期(摊销)。

    间接加载总是比较慢的,因为有更多的指令,直到您进入超标量设计这样的设计,它允许每个周期有多条指令失效。

    一旦你有了这个,这两个例子的最小值就变成了0到1个周期之间的某个值,同样,只要其他的都是理想的。可以说,第二个例子必须有比第一个例子更理想的环境才能达到理论上的最小值,但现在是可能的。

    在某些情况下,你会关心,你可能不会达到最低限度的两个例子。要么分支目标缓存是冷的,要么vtable不在数据缓存中,要么机器无法重新排序指令以充分利用冗余功能单元。

    …这就是分析的用武之地,这通常是个好主意。

    你呢 可以 Noel Llopis's article on data oriented design ,优秀的 Pitfalls of Object-Oriented Programming slides ,和 Mike Acton's grumpy-yet-educational presentations . 现在您突然进入了CPU可能已经满意的模式,如果您正在处理大量数据的话。

    像virtual这样的高级语言特性通常是表达能力和控制能力之间的折衷。不过,我真诚地认为,只要提高对virtual实际在做什么的认识(不要害怕时不时地阅读反汇编视图,并且一定要看看CPU的体系结构手册),你就会倾向于在有意义的时候使用它,而不是在没有意义的时候,如果需要的话,profiler可以覆盖其余的部分。

    关于“不要使用虚拟”或“虚拟使用不太可能产生可测量的影响”的一刀切的说法让我很不高兴。现实通常更为复杂,要么你将处于一种你足够关心或回避它的情况,要么你处于另外95%的情况,除了可能的教育内容之外,它可能不值得关心。

        2
  •  3
  •   jcoder    14 年前

    流水线是主要的方式。

    加载指令、解码指令、执行指令操作和加载间接内存引用可能需要20个时钟周期。但由于采用了流水线,处理器可以在流水线的不同阶段同时执行19条其他指令的部分,从而使每个时钟周期的总吞吐量达到1条指令,而不管通过流水线实际需要多长时间。

        3
  •  1
  •   supercat    14 年前

    我认为处理器有一个特殊的缓存,它保存分支和间接跳转的位置和目标。如果在$12345678处遇到间接跳转,并且上一次遇到它时跳转到了地址$12348765,则处理器甚至可以在解析分支地址之前就开始推测性地执行地址$12348765处的指令。在许多情况下,在函数的内部循环中,特定的间接跳转总是在循环的整个过程中跳转到相同的地址。因此,间接跳转缓存可以避免分支惩罚。

        4
  •  1
  •   ergosys    14 年前

    现代的cpu使用自适应分支预测技术,它可以预测许多间接的跳跃,比如虚拟函数的vtable实现。看到了吗 http://en.wikipedia.org/wiki/Branch_prediction#Prediction_of_indirect_jumps

        5
  •  0
  •   Puppy    14 年前

    如果CPU在缓存中已经有了内存地址,那么执行一条加载指令就很简单了。