我正在分析处理数百万条消息的两种设计之间的差异 . 一种设计使用多态,而另一种设计不使用 - 每个消息将由多态子类型表示 .
我使用VTune描述了这两种设计 . 高级摘要数据似乎有意义 - 多态设计具有更高的“分支误预测”率,更高的CPI和更高的“ICache未命中率”,而不是使用IF语句实现的非多态版本 .
多态设计有一行源代码,如下所示:
object->virtualFunction();
这被称为数百万次(子类型每次都改变) . 由于分支目标误预测/指令未命中,我期待多态设计变慢 . 如上所述,VTune“摘要”选项卡似乎证实了这一点 . 但是,当我查看源代码行旁边的指标时,除了以下内容之外绝对没有指标:
-
填充管道插槽总数 - >退役 - >一般退休
-
填充管道槽自我 - >退休 - >一般退休
-
未填充的管道插槽总数 - >前端绑定 - >前端带宽 - >前端带宽MITE
-
未填充的管道插槽自我 - >前端绑定 - >前端带宽 - >前端带宽MITE
没有分支预测列有数据,指令高速缓存未命中列?
有人可以评论这是否合情合理?对我来说,它不是 - 如果分支目标将不断改变每条消息的多行代码行,怎么会没有分支错误预测或指令缓存未命中统计?
这不能归因于编译器优化/内联,因为编译器不知道要优化的对象的子类型 .
我应该如何使用VTune分析多态性的开销?
2 回答
我将尝试回答问题的第一部分:
实际上有一种编译器可以内联调用虚函数的方法,这是一种有趣的技巧,当我了解它时我感到很惊讶 .
您可以观看this Eric Brumer's talk了解更多详情,从22:30开始,他谈到间接呼叫优化 .
基本上,编译器不是向该虚函数指针发出简单的跳转指令,而是首先添加一些比较,并且对于某些已知的指针值预测所调用的特定虚函数,然后该调用可以在该分支内部内联 . 在这种情况下,不可预测的指针值跳转变成简单的比较分支预测,现代CPU擅长这一点 . 因此,如果大多数调用将进入相同的特定虚函数实现,您可能会看到良好的预测数和低指令缓存未命中数 .
我建议查看该函数调用的反汇编 . 它是否真的使用vtable指针间接跳转到代码,或者它是否通过一些优化避免vtable跳转 .
如果调用没有被编译器优化,那么CPU仍有一些推测方法,挖掘Branch Target Buffer . 例如,如果在相同类型的对象的紧密循环中调用此函数,那么它是否为虚拟可能无关紧要,其地址可能会被预测...
HTH .
您没有在指令本身上看到分支错误预测,因为样本将在分支之后的下一条指令上“聚合” .
所有非精确事件都是如此(最后没有
_PS
) . 人们可以通过检查常规代码配置文件轻松找到它 . 例如,有更高的可能性,人们会发现在一个简单的add
上有更多的CPU_CLK_UNHALTED
样本,而不是在add
之前的重imul
上 .为了查看事件发生的"exact"指令,您必须使用精确的事件,例如
BR_MISP_RETURED.ALL_BRANCHES_PS
.我不是100%肯定这个“问题”的本质,我知道应该可以修复它,但由于某种原因,VTune采样驱动程序的人不想这样做 . 我知道有一个人在过去的6年里一直在与这个问题作斗争,我将其纳入其中帐户每次检查asm VTune配置文件:)
PS . 关于虚拟功能的原始测试 . 我也测试了它,它确实产生了很多分支错误预测 . 对于函数指针也是如此 . 修复它的一种方法是使用模板类,如果可能的话 .