c - 函数指针是否强制清除指令流水线?

标签 c performance x86 function-pointers pipeline

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

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

如果我在 C 中调用一个函数指针,流水线是否足够智能以识别流水线中的指针是一个函数指针,并且它应该跟随该指针以执行下一条指令?或者有一个函数指针会导致管道清理并降低性能吗?

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


编辑

@JensGustedt 写道:

To be a real performance hit for function calls, the function that you call must be extremely brief. If you observe this by measuring your code, you definitively should revisit your design to allow that call to be inlined

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

出于性能原因,我将目标函数写得又小又快。

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

所以,我有一个非常简短的非内联函数。

最佳答案

在某些处理器上,间接分支总是会清除至少一部分流水线,因为它总是会预测错误。对于有序处理器来说尤其如此。

例如,I ran some timings在我们开发的处理器上,比较内联函数调用、直接函数调用和间接函数调用(虚函数或函数指针;它们在这个平台上是相同的)的开销。

我编写了一个微型函数体,并在数百万次调用的紧密循环中对其进行了测量,以确定调用惩罚的成本。 “内联”函数是一个控制组,只测量函数体的成本(基本上是单个加载操作)。直接函数测量正确预测分支的惩罚(因为它是一个静态目标,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 )

如您所见,直接调用比函数体多花费 15 个周期,而虚拟调用 (exactly equivalent to a function pointer call) 比直接调用多花费 22 个周期。这恰好是在流水线开始(取指令)和分支 ALU 结束之间大约有多少流水线阶段。因此,在该架构上,间接分支(也称为虚拟调用)会导致清除 22 个流水线阶段100% 的时间

其他架构可能有所不同。您应该根据直接的经验测量或 CPU 的流水线规范做出这些决定,而不是假设处理器“应该”预测什么,因为实现方式非常不同。在这种情况下,管道清除发生,因为分支预测器无法知道 bctrl 将去哪里,直到它退出。它充其量只能猜测它与上一个 bctrl 的目标相同,而这个特定的 CPU 甚至不会尝试猜测。

关于c - 函数指针是否强制清除指令流水线?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10757167/

相关文章:

c - 使用 C 处理发布请求数据而不消耗不必要的内存的最佳方法

c - C 程序中的 sbrk 函数和指针

c - 32位系统调用表入口点如何映射到x86_64中的SYSCALL_DEFINE

c++ - 2系高效电源 : (2^n) + (2^(n-1)) + (2^(n-2))

c - MPI_Allgather 的负缩放

c - x86-64 处理器的数据类型

mysql - 将数组保存在数据库中或制作不同的列

java - 只有一个部署在 Tomcat 中运行缓慢,另一个运行正常/快速

python - mysql服务器的CPU亲和性

C# (MSIL) 到 native X86 代码?