函数指针是否使程序变慢?

我读了C中的函数指针。大家都说这会让我的程序运行缓慢。 这是真的吗?

我做了一个程序来检查它。 在这两种情况下我都得到了相同的结果。 (测量时间)

那么,使用函数指针是不是很好? 提前致谢。

为了回应一些人。 我说我在比较一个循环的时候“运行缓慢”。 喜欢这个:

int end = 1000; int i = 0; while (i < end) { fp = func; fp (); } 

当你执行这个,我得到了同样的时间,如果我执行这个。

 while (i < end) { func (); } 

所以我认为函数指针没有时间差别,也没有像很多人说的那样让程序运行缓慢。

从性能的angular度来看,从性能的angular度来看,就像在一个循环中多次重复调用函数一样,性能也许并没有什么不同。

对于那些习惯于把C代码视为由“机器语言”与C语言本身非常相似的抽象C机器所执行的东西的人来说,这听起来可能有些奇怪。 在这种情况下,“默认情况下”对函数的间接调用确实比直接调用慢,因为它正式涉及额外的内存访问以确定调用的目标。

但是,在现实生活中,代码由真机执行,并由优化编译器编译,该编译器对底层机器体系结构有相当好的了解,这有助于为特定机器生成最优代码。 而且在很多平台上可能会发现,从一个循环执行函数调用的最有效的方法实际上会导致直接和间接调用的代码相同 ,从而导致两者的相同性能。

考虑一下,例如,x86平台。 如果我们“直接”将直接和间接的调用翻译成机器代码,那么我们可能会得到类似的结果

 // Direct call do-it-many-times call 0x12345678 // Indirect call do-it-many-times call dword ptr [0x67890ABC] 

前者在机器指令中使用立即操作数,实际上通常比后者要快,后者必须从某个独立的存储单元读取数据。

在这一点上,让我们记住,x86架构实际上有更多的方法来为call指令提供一个操作数。 它将目标地址提供给一个寄存器 。 关于这种格式的一个非常重要的事情是,它通常比上述两个都快 。 这对我们意味着什么? 这意味着一个好的优化编译器必须并将利用这个事实。 为了实现上述循环,编译器将尝试在两种情况下通过寄存器使用一个调用。 如果成功,最终的代码可能如下所示

 // Direct call mov eax, 0x12345678 do-it-many-times call eax // Indirect call mov eax, dword ptr [0x67890ABC] do-it-many-times call eax 

请注意,现在重要的部分 – 循环体中的实际调用 – 在两种情况下都完全相同。 毋庸置疑,性能将几乎相同

有人甚至可能会说,无论听起来奇怪,只要间接调用的操作数是在寄存器中提供的,在这个平台上直接调用(在调用中具有立即操作数的call )比间接调用要慢反对被存储在内存中)。

当然,在一般情况下,整个事情并不那么容易。 编译器必须处理寄存器的有限可用性,别名问题等等。但是像你的例子那样简单的情况下(甚至更复杂的情况下)上述优化将由一个好的编译器来执行,并将完全消除循环直接呼叫和循环间接呼叫之间的任何性能差异。 这个优化在C ++中特别适用,当调用一个虚拟函数的时候,因为在一个典型的实现中,所涉及的指针完全由编译器控制,使得它对别名图片和其他相关的东西有了全面的了解。

当然,总是有一个问题,你的编译器是否足够聪明来优化这样的事情…

我想当人们这样说时,他们指的是使用函数指针可能会阻止编译器优化(内联)和处理器优化(分支预测)的事实。 但是,如果函数指针是一种有效的方法来完成你正在做的事情,那么其他任何方法都会有同样的缺点。

除非你的函数指针在一个性能关键的应用程序中或者在一个非常慢的embedded式系统中被紧密的使用,否则这个差别是微不足道的。

通过函数指针调用函数比静态函数调用慢一些,因为前一个调用包括一个额外的指针解引用。 但是,AFAIK在大多数现代化的机器上这种差别是微不足道的(除非是一些资源非常有限的特殊平台)。

函数指针的使用是因为它们可以使程序变得更简单,更清洁,更容易维护(当然使用得当)。 这比弥补可能的很小的速度差。

很多人都提出了一些很好的答案,但我仍然认为有一点是错过的。 函数指针确实增加了一个额外的解引用,使得它们慢几个周期,这个数字可以基于不良的分支预测而增加(顺便说一下,这与函数指针本身几乎没有关系)。 另外通过指针调用的函数不能被内联。 但是人们缺less的是大多数人使用函数指针作为优化。

在c / c ++ API中最常见的函数指针就是callback函数。 造成这么多的API的原因是因为编写一个当事件发生时调用函数指针的系统比其他方法如消息传递更有效率。 我个人也使用函数指针作为更复杂的input处理系统的一部分,键盘上的每个键都有一个通过跳转表映射到它的函数指针。 这使我可以从input系统中删除任何分支或逻辑,只需处理按键即可。

使用函数指针要慢一点,只是调用一个函数,因为它是另一个间接层。 (指针需要被解引用来获取函数的内存地址)。 虽然速度较慢,但​​与其他程序相比(读取文件,写入控制台)可以忽略不计。

如果你需要使用函数指针,使用它们是因为任何试图做同样的事情,但避免使用它们的东西会比使用函数指针更慢,维护性更差。

而且大家都说这会让我的程序运行缓慢。 这是真的吗?

这个说法很可能是错误的。 首先,如果使用函数指针的替代是类似的

 if (condition1) { func1(); } else if (condition2) func2(); } else if (condition3) func3(); } else { func4(); } 

这很可能比仅使用单个函数指针要慢得多。 虽然通过指针调用函数确实有一些(通常是可忽略的)开销,但它通常不是与比较相关的直接函数调用和直通指针调用差异。

其次,没有任何测量,从不优化性能。 知道瓶颈的位置是非常困难的(阅读是不可能的 )知道,有时这可能是相当不直观的(例如,linux内核开发人员已经开始从函数中删除inline关键字,因为它实际上损害了性能)。

在以前的答复很多好点。

不过看看C qsort比较函数。 因为比较函数不能被内联,并且需要遵循标准的基于堆栈的调用约定,所以对于整数键来说,sorting的总运行时间可以是一个数量级 (更准确地说是3-10x),而不是相同的具有直接,可调,打电话。

典型的内联比较将是一系列简单的CMP和可能的CMOV / SET指令。 函数调用还会导致CALL开销,设置堆栈帧,进行比较,拆除堆栈帧并返回结果。 请注意,由于CPUstream水线长度和虚拟寄存器的原因,堆栈操作可能会导致stream水线延迟。 例如,如果在最后修改的eax已经执行的指令(通常在最新的处理器上需要大约12个时钟周期)之前需要say eax的值。 除非CPU可以执行其他指令来等待,否则会发生stream水线失速。