编译器是否优化了通过指针进行的对简单函数的调用?
假设我有一个接受函数指针的函数:
int funct(double (*f)(double));
我向它传递了一个实际上不执行任何操作的函数:
double g(double a) { return 1.0;}
//...
funct(g);
编译器会优化对 g
的调用吗?或者这仍然会有开销吗?如果确实有开销,有多少?是否值得重载函数来接收函数指针和常量值?
Say I have a function that takes a function pointer:
int funct(double (*f)(double));
And I pass it a function that doesn't actually do anything:
double g(double a) { return 1.0;}
//...
funct(g);
Will the compiler optimize out the calls to g
? Or will this still have overhead? If it does have overhead, how much? Enough that it is worth overloading the function to receive both function pointers and constant values?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
较新版本的 GCC(4.4 及更高版本)可以使用选项
-findirect-inlined
内联和优化已知函数指针。仅当 GCC 也知道所有使用该指针的代码时,这才有效。例如,C 库函数
qsort
将不会从这种优化中受益。 qsort 的已编译机器代码位于库中,它需要一个函数指针,并且编译器无法更改它。但是,如果您有自己的 qsort 实现,并将其放在头文件中,或者使用了非常新的 GCC 链接时优化功能,那么 GCC 将能够获取您的调用代码,即函数指向,以及您的 qsort 源代码并将其全部编译在一起,针对您的数据类型和比较函数进行了优化。
现在,唯一真正重要的时候是函数调用开销远大于函数本身的时候。在您的函数示例中,函数不执行任何操作,使用函数指针会产生严重的开销。在我的 qsort 比较示例中,函数指针调用的开销也相当大。但在其他一些应用程序(如 Windows 事件调度)中,这并不重要。
由于您使用的是 C++,因此您应该学习模板。模板函数可以接受所谓的函数对象,它只是一个实现了operator()的对象,并且它可以接受函数指针。传递函数对象将允许 C++ 编译器内联和优化几乎所有涉及的代码。
Newer versions of GCC (4.4 and later) can inline and optimize a known function pointer using the option
-findirect-inlining
. This only works when GCC also knows all of the code that uses the pointer.For example, the C library function
qsort
will not benefit from this optimization. The compiled machine code for qsort is in the library, it expects a function pointer and the compiler cannot change that.However, if you had your own implementation of qsort and you placed it in a header file or you used the very new GCC link-time optimization features, then GCC would be able to take your calling code, the function pointed to, and your qsort source and compile it all together, optimized for your data types and your comparison function.
Now, the only times that this really matters is when the function call overhead is much larger than the function itself. In your example of a function that does nothing much, using a function pointer is serious overhead. In my example of a qsort comparison, the function pointer call is also quite expensive. But in some other application like Windows event dispatch, it hardly matters.
Since you are using C++ you should study templates. A template function can accept a so-called
function object
which is just an object that implementsoperator()
and it can accept function pointers. Passing a function object will allow the C++ compiler to inline and optimize almost all of the code involved.当编译器在编译时知道指针指向哪里时,任何现代编译器都可以(并且将)轻松优化通过函数指针进行的(即内联)调用。
在您的具体示例中,一般情况下,无法在调用点的编译时预测指针的值,因为它是从外部作为函数
funct
的参数传递的。在函数funct
本身小到足以内联的情况下,运行时参数将被消除,并且funct
的整个代码将在中“溶解”函数的调用者上下文。如果指针的值在调用者上下文中已知,那么编译器可以轻松地消除通过指针的调用。
最后,如果函数 funct 相对较重(意味着它不会内联),那么您可能应该期望编译器通过 funct 内部的指针生成普通的运行时调用。在这种情况下也存在调用消除的可能性,因为理论上编译器可以为
g
的每个编译时值生成单独版本的funct
。例如,如果您仅使用参数
f
的两个可能参数来调用which,那么编译器可以将其“减少”为两个
没有函数指针参数的函数,并直接调用
g1
和g2
里面。 (当然,这种优化不以任何方式与函数指针相关,并且可以应用于仅与一小部分固定参数集一起使用的任何函数参数。事实上,这类似于 C++ 模板。)不知道有任何编译器会做类似的事情。Any modern compiler can (and will) easily optimize out (i.e. inline) calls made through function pointers is situations when the compiler knows where the pointer is pointing to at compile time.
In your specific example in general case the value of the pointer cannot be predicted at compile time at the point of the call, since it is passed from outside as a parameter of function
funct
. In situations when the functionfunct
itself is small enough to get inlined, the run-time parameter gets eliminated and the whole code offunct
gets "dissolved" in thefunct
s caller context. In the value of the pointer is known in that caller context, then, again, the compiler can easily eliminate the call through the pointer.Finally, if the function
funct
is relatively heavy (meaning it does not get inlined), the you should probably expect the compiler to generate an ordinary run-time call through the pointer from inside offunct
. The potential for call elimination exists in this case as well, since the compiler can theoretically generate a separate version offunct
for each compile-time value ofg
. For example, if you havewhich is called with only two possible arguments for parameter
f
then the compiler can "reduce" it into two functions
without a function pointer parameters and with direct calls to either
g1
andg2
inside. (Of course, this optimization is not in any way tied to function pointers and can be applied to any function parameter that is used with only a small fixed set of arguments. In fact, this is similar to C++ templates.) But I'm not aware of any compilers that would do anything like that.编译器可能不会优化它,因为函数
funct
可以接收指向不同函数的指针,而不仅仅是g
,而且它们不必来自同一个编译单元(因此编译器不能假设它知道所有可能的调用)。您需要对代码进行基准测试,看看您所讨论的优化是否必要,如果有必要,那就去做吧。但除非
funct
多次调用g
,否则我不认为这很重要。The compiler will probably not optimize it, because the function
funct
can receive pointers to different functions, not justg
, and they don't have to come from the same compiling unit (thus the compiler cannot assume it knows about all the possible calls).You need to benchmark your code to see if the optimization you're talking about is necessary, and if it is - just do it. But unless
funct
callsg
a lot, I wouldn't expect this to matter.