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

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

我试图理解“即使在内部循环”部分。具体而言,CPU在相同数量的时钟周期内执行两个代码(虚拟与非虚拟或其他间接级别)的实施是什么?我知道说明管道和缓存,但是如何在与非虚拟呼叫相同的时钟周期内执行虚拟呼叫呢?间接方式如何“丢失”?

有帮助吗?

解决方案

缓存(例如 分支目标缓存),平行负载单位(管道的一部分,但也没有拖延管道的“击中”之类的东西)和 排序执行 可能有助于改变 load-load-branch 进入更接近固定的东西 branch. 。在管道的解码或分支预测阶段中,指令折叠/消除(该术语是什么?)也可能会造成。

但是,所有这些都依赖许多不同的东西:有多少个不同的分支目标(例如,您可能会触发多少个不同的虚拟过载),您循环循环多少个(分支目标缓存“温暖”? iCache/dcache怎么样?),虚拟表或间接表如何在内存中列出(它们是友好的缓存,还是每个新的VTable负载可能驱逐旧的VTable?),是否由于缓存而反复无效。多重乒乓球等...

(免责声明:我绝对不是这里的专家,我的很多知识来自研究固定嵌入式处理器,因此其中一些是推断。如果您有更正,请随时发表评论!)

当然,确定特定程序是否会成为问题的正确方法。如果可以的话,请在硬件计数器的帮助下进行 - 他们可以告诉您很多有关管道各个阶段的情况。


编辑:

正如汉斯(Hans)在上述评论中指出的那样 现代CPU内部循环间接优化, ,让这两件事花费相同的时间的关键是能够有效地“退休”每个周期“退休”。消除指导可以帮助解决这个问题,但是 超级标准设计 可能更重要(在Miss下受到打击是一个非常小而特定的例子,完全冗余的负载单元可能是一个更好的例子)。

让我们采取理想的情况,并假设直接分支只是一个指示:

branch dest

...间接分支是三个(也许您可以一分为二,但它大于一个):

load vtable from this
load dest from vtable
branch dest

让我们假设一个绝对完美的情况: *这和整个VTable在L1缓存中,L1缓存足够快,可以支持两个负载的每个说明成本。 (您甚至可以假设处理器对负载进行了重新排序,并用较早的说明将它们互混合,以便在分支之前完成时间完成;对于此示例,这无关紧要。)还假设分支目标缓存是热的,并且没有管道分支机构的冲洗成本,分支指令降至单个周期(摊销)。

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

第二个示例的理论最小值,缺乏指令消除或冗余功能单元或将允许每个周期退休的某些指令的东西是3个周期(有3个指令)!

间接负载始终会较慢,因为有更多的说明,直到您进入SuperScalar Design之类的东西,该设计允许每个周期退休。

一旦有了这个,两个示例的最低限度就变成了0到1个周期之间的一件事,只要其他一切都是理想的。可以说,您必须有更理想的情况才能实际达到该理论最低限度,而不是第一个示例,但现在有可能。

在您关心的某些情况下,您可能不会达到最低限度。分支目标缓存将很冷,或者VTable不会在数据缓存中,或者机器将无法重新排序指令以充分利用冗余功能单元。

...这是分析进来的地方,无论如何,这通常是一个好主意。

能够 首先,只是拥护一些关于虚拟的偏执狂。看 Noel Llopis关于以数据为导向设计的文章, ,优秀 面向对象的编程幻灯片的陷阱, , 和 迈克·阿克顿(Mike Acton)的脾气暴躁的教育演讲. 。现在,如果您正在处理大量数据,那么您突然进入了CPU可能已经满意的模式。

诸如虚拟的高级语言特征通常是表现力和控制之间的权衡。不过,老实说,我只想提高您对虚拟实际在做什么的认识(不要害怕不时阅读拆卸视图,并且肯定会窥视您的CPU架构手册),您会倾向于使用它当它是有道理的,而不是在没有的情况下,探索者可以在需要时覆盖其余的。

关于“不要使用虚拟”或“虚拟使用的单一适合陈述,不太可能产生可衡量的差异”使我感到脾气暴躁。现实通常更为复杂,要么您要处于足够关心或避免的情况下,要么您在其他95%的人中可能不值得关心,除了可能的教育内容。

其他提示

管道是主要方式。

可能需要20个时钟周期来加载指令,解码,执行操作并加载间接内存引用。但是,由于Pipleline,处理器可以在管道的不同阶段同时执行19个其他说明的部分,从而为每个时钟周期提供1个指令的总体吞吐量,而不管通过管道提供该说明的实际时间。

发生了什么,我认为处理器具有一个特殊的缓存,可容纳分支机构和间接跳跃的位置和目标。如果遇到$ 12345678的间接跳跃,并且最后一次遇到的时,它可以在$ 12348765上拨打$ 12348765,则处理器甚至可以在地址$ 12348765开始投机执行指令,即在许多情况下,在函数的内环内,在整个环路的整个过程中,特定的间接跳跃总是会跳到相同的地址。因此,间接悬浮缓存可以避免分支的惩罚。

现代CPU使用一种自适应分支预测技术,可以预测许多间接跳跃,例如通过虚拟功能实现可获得的。看 http://en.wikipedia.org/wiki/branch_prediction#prediction_of_indirect_jumps

如果CPU已经在缓存中具有内存地址,则执行加载指令是微不足道的。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top