abe*_*nky 23 c performance x86 pipeline function-pointers
现代CPU具有广泛的流水线操作,也就是说,它们在实际执行指令之前很久就会加载必要的指令和数据.
有时,加载到管道中的数据会失效,必须清除管道并重新加载新数据.重新填充管道所需的时间可能相当长,并导致性能下降.
如果我在C中调用一个函数指针,那么管道是否足够智能以实现管道中的指针是一个函数指针,并且它应该跟随该指针用于下一个指令?或者是否有一个函数指针导致管道清除并降低性能?
我在C中工作,但我想这在C++中更为重要,因为许多函数调用都是通过v-tables进行的.
要成为函数调用的真正性能,您调用的函数必须非常简短.如果您通过测量代码来观察这一点,那么您最终应该重新审视您的设计以允许内联调用
不幸的是,这可能是我陷入的陷阱.
出于性能原因,我编写了小而快的目标函数.
但是它被函数指针引用,因此它可以很容易地被其他函数替换(只需使指针引用一个不同的函数!).因为我通过函数指针引用它,所以我认为它不能内联.
所以,我有一个非常简短,没有内联的功能.
Cra*_*rks 12
在某些处理器上,间接分支将始终清除至少部分管道,因为它总是会错误预测.对于有序处理器尤其如此.
例如,我在我们开发的处理器上运行了一些时序,比较内联函数调用与直接函数调用的开销,而不是间接函数调用(虚函数或函数指针;它们在此平台上是相同的).
我写了一个很小的函数体,并在数百万次调用的紧密循环中测量它,以确定只是呼叫惩罚的成本."内联"功能是一个控制组,仅测量功能体的成本(基本上是单个负载操作).直接函数测量了正确预测分支的惩罚(因为它是一个静态目标,而PPC的预测器总能得到正确的)和函数序言.间接函数测量bctrl
间接分支的惩罚.
614,400,000函数调用:
inline: 411.924 ms ( 2 cycles/call )
direct: 3406.297 ms ( ~17 cycles/call )
virtual: 8080.708 ms ( ~39 cycles/call )
Run Code Online (Sandbox Code Playgroud)
如您所见,直接调用比函数体多15个周期,虚拟调用(完全等效于函数指针调用)比直接调用花费多22个周期.这恰好大约是在pipline开始(取指令)和分支ALU结束之间有多少个流水线级.因此,在这种体系结构中,间接分支(也称为虚拟调用)在100%的时间内导致清除22个流水线阶段.
其他架构可能会有所不同.您应该通过直接的经验测量或CPU的管道规范来做出这些决定,而不是关于处理器"应该"预测的假设,因为实现是如此不同.在这种情况下,管道清除发生是因为分支预测器无法知道bctrl在退役之前将去往何处.充其量它可以猜测它是与最后一个bctrl相同的目标,而这个特定的CPU甚至没有尝试这种猜测.
调用函数指针与在C++中调用虚方法没有根本的区别,也就是说它与返回根本不同.处于预测状态的处理器将识别出一个分支通过指针即将出现,并将决定它是否可以在预取管道中安全有效地解析指针并遵循该路径.这显然比遵循常规相对分支更加困难和昂贵,但是,由于间接分支在现代程序中如此常见,因此大多数处理器都会尝试这种分支.
正如Oli所说,只有在条件分支上存在错误预测时才"清除"管道,这与分支是通过偏移还是通过可变地址无关.但是,处理器可能具有根据分支地址的类型进行不同预测的策略 - 通常,由于可能存在错误的地址,处理器不太可能积极地遵循条件分支的间接路径.
归档时间: |
|
查看次数: |
3249 次 |
最近记录: |