C语言 函数指针会使程序变慢吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2438539/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does Function pointer make the program slow?
提问by drigoSkalWalker
I read about function pointers in C. And everyone said that will make my program run slow. Is it true?
我读过 C 中的函数指针。每个人都说这会使我的程序运行缓慢。这是真的吗?
I made a program to check it. And I got the same results on both cases. (measure the time.)
我做了一个程序来检查它。我在这两种情况下都得到了相同的结果。(测量时间。)
So, is it bad to use function pointer? Thanks in advance.
那么,使用函数指针是不是不好呢?提前致谢。
To response for some guys. I said 'run slow' for the time that I have compared on a loop. like this:
回应一些人。我在循环中比较的时候说“慢跑”。像这样:
int end = 1000;
int i = 0;
while (i < end) {
fp = func;
fp ();
}
When you execute this, i got the same time if I execute this.
当您执行此操作时,如果我执行此操作,我将获得相同的时间。
while (i < end) {
func ();
}
So I think that function pointer have no difference of time and it don't make a program run slow as many people said.
所以我认为函数指针没有时间差异,它不会像许多人所说的那样使程序运行缓慢。
回答by AnT
You see, in situations that actually matter from the performance point of view, like calling the function repeatedly many times in a cycle, the performance might not be different at all.
你看,在从性能的角度来看实际上很重要的情况下,比如在一个循环中多次重复调用函数,性能可能根本没有区别。
This might sound strange to people, who are used to thinking about C code as something executed by an abstract C machine whose "machine language" closely mirrors the C language itself. In such context, "by default" an indirect call to a function is indeed slower than a direct one, because it formally involves an extra memory access in order to determine the target of the call.
对于习惯于将 C 代码视为由抽象 C 机器执行的东西的人来说,这可能听起来很奇怪,其“机器语言”与 C 语言本身密切相关。在这种情况下,“默认情况下”对函数的间接调用确实比直接调用慢,因为它正式涉及额外的内存访问以确定调用的目标。
However, in real life the code is executed by a real machine and compiled by an optimizing compiler that has a pretty good knowledge of the underlying machine architecture, which helps it to generate the most optimal code for that specific machine. And on many platforms it might turn out that the most efficient way to perform a function call from a cycle actually results in identicalcode for both direct and indirect call, leading to the identical performance of the two.
然而,在现实生活中,代码是由真机执行并由优化编译器编译的,该编译器对底层机器架构非常了解,这有助于为该特定机器生成最佳代码。在许多平台上,结果可能是,从循环中执行函数调用的最有效方法实际上会导致直接和间接调用的代码相同,从而导致两者的性能相同。
Consider, for example, the x86 platform. If we "literally" translate a direct and indirect call into machine code, we might end up with something like this
例如,考虑 x86 平台。如果我们“从字面上”将直接和间接调用翻译成机器代码,我们可能会得到这样的结果
// Direct call
do-it-many-times
call 0x12345678
// Indirect call
do-it-many-times
call dword ptr [0x67890ABC]
The former uses an immediate operand in the machine instruction and is indeed normally faster than the latter, which has to read the data from some independent memory location.
前者在机器指令中使用立即数操作数,确实通常比后者快,后者必须从某个独立的内存位置读取数据。
At this point let's remember that x86 architecture actually has one more way to supply an operand to the callinstruction. It is supplying the target address in a register. And a very important thing about this format is that it is normally faster than both of the above. What does this mean for us? This means that a good optimizing compiler must and will take advantage of that fact. In order to implement the above cycle, the compiler will try to use a call through a register in bothcases. If it succeeds, the final code might look as follows
在这一点上,让我们记住 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
Note, that now the part that matters - the actual call in the cycle body - is exactly and precisely the same in both cases. Needless to say, the performance is going to be virtually identical.
请注意,现在重要的部分 - 循环体中的实际调用 - 在两种情况下完全相同。不用说,性能几乎相同。
One might even say, however strange it might sound, that on this platform a direct call (a call with an immediate operand in call) is slowerthan an indirect call as long as the operand of the indirect call is supplied in a register(as opposed to being stored in memory).
甚至有人可能会说,无论听起来多么奇怪,只要间接调用的操作数在寄存器中提供(相反call),在这个平台上,直接调用(在 中使用立即操作数的调用)比间接调用慢存储在内存中)。
Of course, the whole thing is not as easy in general case. The compiler has to deal with limited availability of registers, aliasing issues etc. But is such simplistic cases as the one in your example (and even in much more complicated ones) the above optimization will be carried out by a good compiler and will completely eliminate any difference in performance between a cyclic direct call and a cyclic indirect call. This optimization works especially well in C++, when calling a virtual function, since in a typical implementation the pointers involved are fully controlled by the compiler, giving it full knowledge of the aliasing picture and other relevant stuff.
当然,在一般情况下,整个事情并不那么容易。编译器必须处理寄存器的有限可用性、别名问题等。但是在您的示例中(甚至在更复杂的情况下)这样简单的情况下,上述优化将由一个好的编译器执行,并将完全消除循环直接调用和循环间接调用之间的任何性能差异。当调用虚函数时,这种优化在 C++ 中特别有效,因为在典型的实现中,所涉及的指针完全由编译器控制,使其完全了解别名图片和其他相关内容。
Of course, there's always a question of whether your compiler is smart enough to optimize things like that...
当然,总是存在一个问题,即您的编译器是否足够聪明来优化这样的事情......
回答by Tyler McHenry
I think when people say this they're referring to the fact that using function pointers may prevent compiler optimizations (inlining) and processor optimizations (branch prediction). However, if function pointers are an effective way to accomplish something that you're trying to do, chances are that any other method of doing it would have the same drawbacks.
我认为当人们这么说时,他们指的是使用函数指针可能会阻止编译器优化(内联)和处理器优化(分支预测)。但是,如果函数指针是完成您正在尝试做的事情的有效方法,那么任何其他方法都有可能具有相同的缺点。
And unless your function pointers are being used in tight loops in a performance critical application or on a very slow embedded system, chances are the difference is negligible anyway.
除非您的函数指针在性能关键应用程序或非常慢的嵌入式系统中的紧密循环中使用,否则无论如何差异都可以忽略不计。
回答by hlovdal
And everyone said that will make my program run slow. Is it true?
每个人都说这会让我的程序运行缓慢。这是真的吗?
Most likely this claim is false. For one, if the alternative to using function pointers are something like
这种说法很可能是错误的。一方面,如果使用函数指针的替代方法类似于
if (condition1) {
func1();
} else if (condition2)
func2();
} else if (condition3)
func3();
} else {
func4();
}
this is most likely relativelymuch slower than just using a single function pointer. While calling a function through a pointer does have some (typically neglectable) overhead, it is normally not the direct-function-call versus through-pointer-call difference that is relevant to compare.
这是最有可能相对比只用一个单一的函数指针慢得多。虽然通过指针调用函数确实有一些(通常可以忽略的)开销,但与比较相关的通常不是直接函数调用与通过指针调用的差异。
And secondly, never optimize for performance without any measurements. Knowing where the bottlenecks are is very difficult (read impossible) to know and sometimes this can be quite non-intuitively (for instance the linux kernel developers have started removing the inlinekeyword from functions because it actually hurt performance).
其次,永远不要在没有任何测量的情况下优化性能。知道瓶颈在哪里是非常困难的(读起来不可能),有时这可能非常不直观(例如,Linux 内核开发人员已经开始inline从函数中删除关键字,因为它实际上会损害性能)。
回答by Beanz
A lot of people have put in some good answers, but I still think there's a point being missed. Function pointers do add an extra dereference which makes them several cycles slower, that number can increase based on poor branch prediction (which incidentally has almost nothing to do with the function pointer itself). Additionally functions called via a pointer cannot be inlined. But what people are missing is that most people use function pointers as an optimization.
很多人已经给出了一些很好的答案,但我仍然认为有一点被遗漏了。函数指针确实添加了一个额外的取消引用,这使它们慢了几个周期,这个数字可能会基于糟糕的分支预测而增加(顺便说一句,这几乎与函数指针本身无关)。此外,不能内联通过指针调用的函数。但是人们缺少的是大多数人使用函数指针作为优化。
The most common place you will find function pointers in c/c++ APIs is as callback functions. The reason so many APIs do this is because writing a system that invokes a function pointer whenever events occur is much more efficient than other methods like message passing. Personally I've also used function pointers as part of a more-complex input processing system, where each key on the keyboard has a function pointer mapped to it via a jump table. This allowed me to remove any branching or logic from the input system and merely handle the key press coming in.
在 c/c++ API 中,函数指针最常见的地方是作为回调函数。如此多的 API 这样做的原因是,编写一个在事件发生时调用函数指针的系统比其他方法(如消息传递)高效得多。就个人而言,我还使用函数指针作为更复杂的输入处理系统的一部分,其中键盘上的每个键都有一个通过跳转表映射到它的函数指针。这使我可以从输入系统中删除任何分支或逻辑,而只处理输入的按键。
回答by Péter T?r?k
Calling a function via a function pointer is somewhatslower than a static function call, since the former call includes an extra pointer dereferencing. But AFAIK this difference is negligible on most modern machines (except maybe some special platforms with very limited resources).
通过函数指针调用的函数是有点慢于静态函数调用,因为前者调用包括一个额外的指针废弃。但是 AFAIK 这种差异在大多数现代机器上可以忽略不计(除了一些资源非常有限的特殊平台)。
Function pointers are used because they can make the program much simpler, cleaner and easier to maintain (when used properly, of course). This more than makes up for the possible very minor speed difference.
使用函数指针是因为它们可以使程序更简单、更清晰、更易于维护(当然,如果使用得当)。这足以弥补可能的非常小的速度差异。
回答by Yacoby
Using a function pointer is slower that just calling a function as it is another layer of indirection. (The pointer needs to be dereferenced to get the memory address of the function). While it is slower, compared to everything else your program may do (Read a file, write to the console) it is negligible.
使用函数指针比调用函数要慢,因为它是另一个间接层。(指针需要解引用才能得到函数的内存地址)。虽然它速度较慢,但与您的程序可能执行的所有其他操作(读取文件,写入控制台)相比,它可以忽略不计。
If you need to use function pointers, use them because anything that tries to do the same thing but avoids using them will be slower and less maintainable that using function pointers.
如果您需要使用函数指针,请使用它们,因为任何试图做同样事情但避免使用它们的东西都会比使用函数指针更慢且更不易维护。
回答by user267027
A lot of good points in earlier replies.
之前的回复有很多优点。
However take a look at C qsort comparison function. Because the comparison function cannot be inlined and needs to follow standard stack based calling conventions, the total running time for the sort can be an order of magnitude(more exactly 3-10x) slower for integer keys, than otherwise same code with a direct, inlineable, call.
但是看看 C qsort 比较函数。因为比较函数不能内联并且需要遵循基于标准堆栈的调用约定,所以对于整数键,排序的总运行时间可能比其他相同的代码慢一个数量级(更准确地说是 3-10倍)内联,调用。
A typical inlined comparison would be a sequence of simple CMP and possibly CMOV/SET instruction. A function call also incurs the overhead of a CALL, setting up stack frame, doing the comparison, tearing down stack frame and returning the result. Note, that the stack operations can cause pipeline stalls due to CPU pipeline length and virtual registers. For example if value of say eax is needed before the instruction that last modified eax has finished executing (which typically takes about 12 clock cycles on the newest processors). Unless the CPU can execute other instructions out of order to wait for that, a pipeline stall will occur.
典型的内联比较将是一系列简单的 CMP 和可能的 CMOV/SET 指令。函数调用还会产生 CALL 的开销,设置堆栈帧,进行比较,拆除堆栈帧并返回结果。请注意,由于 CPU 管道长度和虚拟寄存器,堆栈操作可能会导致管道停顿。例如,如果在最后修改的 eax 指令完成执行之前需要 say eax 的值(在最新的处理器上通常需要大约 12 个时钟周期)。除非 CPU 可以无序执行其他指令来等待,否则会发生流水线停顿。
回答by Praxeolitic
Possibly.
可能。
The answer depends on what the function pointer is being used for and hence what the alternatives are. Comparing function pointer calls to direct function calls is misleading if a function pointer is being used to implement a choice that's part of our program logic and which can't simply be removed. I'll go ahead and nonetheless show that comparison and come back to this thought afterwards.
答案取决于函数指针的用途以及替代方案。如果函数指针用于实现作为我们程序逻辑的一部分并且不能简单地删除的选择,则将函数指针调用与直接函数调用进行比较会产生误导。尽管如此,我还是会继续进行比较,然后再回到这个想法。
Function pointer calls have the most opportunity to degrade performance compared to direct function calls when they inhibit inlining. Because inlining is a gateway optimization, we can craft wildly pathological cases where function pointers are made arbitrarily slower than the equivalent direct function call:
与禁止内联的直接函数调用相比,函数指针调用最有可能降低性能。因为内联是一种网关优化,我们可以设计出非常病态的情况,其中函数指针的速度比等效的直接函数调用慢:
void foo(int* x) {
*x = 0;
}
void (*foo_ptr)(int*) = foo;
int call_foo(int *p, int size) {
int r = 0;
for (int i = 0; i != size; ++i)
r += p[i];
foo(&r);
return r;
}
int call_foo_ptr(int *p, int size) {
int r = 0;
for (int i = 0; i != size; ++i)
r += p[i];
foo_ptr(&r);
return r;
}
Code generatedfor call_foo():
call_foo(int*, int):
xor eax, eax
ret
Nice. foo()has not only been inlined, but doing so has allowed the compiler to eliminate the entire preceding loop! The generated code simply zeroes out the return register by XORing the register with itself and then returns. On the other hand, compilers will have to generate code for the loop in call_foo_ptr()(100+ lines with gcc 7.3) and most of that code effectively does nothing (so long as foo_ptrstill points to foo()). (In more typical scenarios, you can expect that inlining a small function into a hot inner loop might reduce execution time by up to about an order of magnitude.)
好的。foo()不仅被内联,而且这样做允许编译器消除整个前面的循环!生成的代码通过将寄存器与自身进行异或来简单地将返回寄存器清零,然后返回。另一方面,编译器将不得不为循环生成代码call_foo_ptr()(使用 gcc 7.3 的 100 多行),并且大部分代码实际上什么都不做(只要foo_ptr仍然指向foo())。(在更典型的场景中,您可以预期将一个小函数内联到热内循环中可能会将执行时间减少大约一个数量级。)
So in a worst case scenario, a function pointer call is arbitrarily slower than a direct function call, but this is misleading. It turns out that if foo_ptrhad been const, then call_foo()and call_foo_ptr()would have generated the same code. However, this would require us to give up the opportunity for indirection provided by foo_ptr. Is it "fair" for foo_ptrto be const? If we're interested in the indirection provided by foo_ptr, then no, but if that's the case, then a direct function call is not a valid option either.
因此,在最坏的情况下,函数指针调用比直接函数调用慢得多,但这是一种误导。事实证明,如果foo_ptrwas const, thencall_foo()和call_foo_ptr()将生成相同的代码。但是,这将要求我们放弃foo_ptr. 它是“公平”的foo_ptr是const?如果我们对 提供的间接感兴趣foo_ptr,那么不,但如果是这种情况,那么直接函数调用也不是有效的选择。
If a function pointer is being used to provide useful indirection, then we can move the indirection around or in some cases swap out function pointers for conditionals or even macros, but we can't simply remove it. If we've decided that function pointers are a good approach but performance is a concern, then we typically want to pull indirection up the call stack so that we pay the cost of indirection in an outer loop. For example, in the common case where a function takes a callback and calls it in a loop, we might try moving the innermost loop into the callback (and changing the responsibility of each callback invocation accordingly).
如果函数指针被用于提供有用的间接引用,那么我们可以移动间接引用,或者在某些情况下将函数指针替换为条件甚至宏,但我们不能简单地删除它。如果我们已经确定函数指针是一个很好的方法,但性能是一个问题,那么我们通常希望将间接性拉上调用堆栈,以便我们在外部循环中支付间接性成本。例如,在函数接受回调并在循环中调用它的常见情况下,我们可能会尝试将最内层的循环移动到回调中(并相应地更改每个回调调用的职责)。

