函数指针强制指令管道清除吗?

abe*_*nky 23 c performance x86 pipeline function-pointers

现代CPU具有广泛的流水线操作,也就是说,它们在实际执行指令之前很久就会加载必要的指令和数据.

有时,加载到管道中的数据会失效,必须清除管道并重新加载新数据.重新填充管道所需的时间可能相当长,并导致性能下降.

如果我在C中调用一个函数指针,那么管道是否足够智能以实现管道中的指针是一个函数指针,并且它应该跟随该指针用于下一个指令?或者是否有一个函数指针导致管道清除并降低性能?

我在C中工作,但我想这在C++中更为重要,因为许多函数调用都是通过v-tables进行的.


编辑

@JensGustedt写道:

要成为函数调用的真正性能,您调用的函数必须非常简短.如果您通过测量代码来观察这一点,那么您最终应该重新审视您的设计以允许内联调用

不幸的是,这可能是我陷入的陷阱.

出于性能原因,我编写了小而快的目标函数.

但是它被函数指针引用,因此它可以很容易地被其他函数替换(只需使指针引用一个不同的函数!).因为我通过函数指针引用它,所以我认为它不能内联.

所以,我有一个非常简短,没有内联的功能.

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甚至没有尝试这种猜测.

  • 这是一篇非常有趣的文章,但应该强调的是,这取决于架构。从您的帖子(顺便说一句,链接已过时)来看,您似乎正在测试 IBM Xenon 处理器。我还假设同样的情况也可能适用于低端嵌入式微控制器,但自 2004 年左右以来,英特尔和 AMD 处理器已经对间接跳跃进行了某种预测。即使今天的 ARM 处理器也不应该在没有错误预测的情况下清除整个管道。 (2认同)

Hot*_*cks 9

调用函数指针与在C++中调用虚方法没有根本的区别,也就是说它与返回根本不同.处于预测状态的处理器将识别出一个分支通过指针即将出现,并将决定它是否可以在预取管道中安全有效地解析指针并遵循该路径.这显然比遵循常规相对分支更加困难和昂贵,但是,由于间接分支在现代程序中如此常见,因此大多数处理器都会尝试这种分支.

正如Oli所说,只有在条件分支上存在错误预测时才"清除"管道,这与分支是通过偏移还是通过可变地址无关.但是,处理器可能具有根据分支地址的类型进行不同预测的策略 - 通常,由于可能存在错误的地址,处理器不太可能积极地遵循条件分支的间接路径.

  • 对于详细说明,假设处理器试图在每个周期(他们这样做)获取四个或更多指令.由于指令指针现在奇怪地移动,因此分支会出现问题,因此处理器必须预测它将去往何处.它使用两种结构:分支预测器和分支目标缓冲器(BTB).对于间接分支,实际成本在BTB中,因为它通常只存储分支的最后一个目标.因此,下次处理器遇到分支指令时(在这种情况下,调用函数指针),处理器将采用与上次相同的调用. (3认同)
  • CPU 通常总是积极地遵循其预测,但这并不意味着不会出现不同类型的错误预测:现代芯片可能会出现各种“严重程度”的错误预测。例如,在具有复杂、缓慢预测器的芯片上,前端可能首先根据当前获取地址进行快速预测,在一两个周期后到达较慢但更好的预测。这种类型的重新引导可能完全在前端处理,从而导致获取/解码中出现几个泡沫周期,但不涉及 ROB 或任何复杂的 OoO 状态回滚。 (2认同)