函子是否比函数指针更快?

62

根据Scott Meyers的说法,C++比C更擅长的一个领域是函数对象比函数指针更快。他说这是因为函数对象被内联了,这增加了速度。

我有两个问题:

  1. 我们如何验证函数对象是否真的被内联了?我们可以在实践中验证吗?

  2. 函数对象的内联是否取决于我们使用的编译器,还是所有编译器都像这样执行?


注:原文中的HTML标签已被保留。

lto(连接时优化)应该能够将一个函数及其在不同编译单元中的使用内联。 - vrdhn
2
函数对象不能被内联化(inline);它们不是代码。它们的成员函数,通常是函数调用运算符(operator()),才能够内联化。 - Pete Becker
这个问题是关于函数对象吗?如果不是,能否更改标题? - wchargin
你能提供一个精确的参考吗?(Scott Meyers的书名,章节,段落) - Bludzee
3个回答

80

C++和C标准给编译器留下了很多自由。 编译器可以在每个指令之间计数10亿次,也可以只在整数中包含质数时这样做。

好的“真实”编译器不会这样做。 这是一个实现质量问题。

将函数对象内联到类似于std::sort的东西中是每个真正的编译器都会做的事情。 在这些情况下,很容易检测出需要内联的内容,因为类型信息随着其代码一起传递。

使用函数指针进行此操作更加困难。 如果将所有内容转换为void*char*指针,则更加困难。

实际上,使用C样式调用qsort与使用C ++样式调用std::sort相比可能会导致显着优势。

qsort大约比std::sort慢2倍,如此处所示,在对随机排列的整数进行排序的极其简单的情况下。

检查实际的汇编代码输出主要是一个细节,并且工作量很大,回报很少。 使用具体的实际示例可以让您了解影响有多大。

所有3个clang、gcc和MSVC都能够使std::sort比它们的qsort明显更快。 由于这是一种简单的优化,而将函数指针优化为内联调用则不是,因此您预计较小的编译器在qsort方面可能不会比这更好。


3
使用模板可以更轻松地传播类型信息和进行内联,因为模板基本上是"复制粘贴"的特殊化代码生成机制。(缺点是二进制文件更大,编译时间更长,就像您复制了代码一样。) - usr
2
我同意@usr的观点,使用模板的区别在于实例可以更好地适应调用情况的细节,而不是像编译一次的库函数那样必须为各种情况运行相同的代码。如果在C语言中通过宏来实现qsort而不是库函数,则编译器可以优化每个实例,尽管使用了函数指针(只要在每次调用时清楚指向哪个函数),结果将与C++中的std::sort一样快。 - Marc van Leeuwen
@usr 图灵坑洼中一切皆有可能。宏并不完全是图灵完备的,但你可以接近。重点是实现起来可能会更困难。也许实际的方法是通过宏分别编写具有硬编码步幅和比较函数的自定义qsort,然后分别调用它们。两步走。我相信这就是我在上面链接中找到的东西。我最初设想的是一个qsort调用,它展开为C代码,内联实现qsort(这也是可行的,但更加疯狂)。 - Yakk - Adam Nevraumont
链接和你的结论都没有回答问题。std::sort更快,因为它既不使用函数对象也不使用函数指针,而是直接比较元素,正如@usr所指出的那样。 - YvesgereY
2
@YvesgereY 不,std::sort 有一组使用 < 进行比较的重载函数,还有一组使用传入的函数对象进行比较的重载函数。将 std::less<void>{}less<void> 是一个无状态函数对象,调用 <)作为第二个函数对象传递给它,结果与第一个仅使用 < 的函数相同(忽略一些奇怪的边界情况)。直接使用 < 并不是它更快的原因,因为使用间接 < 也同样快速。注:(set of) 是指在 C++17 中添加的并行 std::sort - Yakk - Adam Nevraumont
显示剩余3条评论

18
  1. 我们如何验证函数对象是否已经被内联了?我们可以在实践中验证吗?

可以,检查最终生成的汇编代码。

  1. 函数对象内联依赖于我们使用的编译器,还是所有编译器都会这样做?

它严重依赖于编译器实现和所使用的优化级别。
因此,并不保证特定的编译器(链接器)会这样做。

不过,通过函数指针进行的调用是无法被内联的。


据他说,函数对象已经被内联了,因此速度有所提升。

在我看来,“函数对象已被内联”更好地理解为(或者听到的,我不知道这个引用来源于哪里):

函数对象可被内联,但通过函数指针进行的调用则不能。


它在很大程度上取决于编译器的实现...因此我们可以得出结论,Scott Meyers的说法并不是绝对正确的吗?我们是否可能有比函数指针更慢的函数对象,这是一个因情况而异的问题,对吧? - user7140484
9
“所以我们可以得出结论,Scott Meyers 的说法并不是绝对的真理?”你会相信谁会说“绝对的真理”呢?对于大多数现代C++编译器来说可能是真的,但仍然取决于优化级别和其他情况,编译器决定是否内联代码。 - πάντα ῥεῖ
1
实际上,通过函数指针进行内联调用是可能的,前提是所述函数(例如,通过指针调用的函数)的实现可用。这里有一个例子,请参见[这里](https://godbolt.org/g/8rT7kZ)。我记得在一个更大的程序中看到过这种情况,当时十分震惊。但基本要点仍然成立:这取决于特定的编译器和优化选项 - 在这种情况下,使用“-O0”运行不会将调用内联。 - Daniel Kamil Kozar
@Daniel 谢谢你指出这个问题,我从没想过编译器会做这件事情。 - πάντα ῥεῖ

1

是的,函数对象可能会导致更快的代码。但确保这一点的唯一方法是进行基准测试。

  1. 文档中说:“GCC可能由于很多原因无法内联函数;可以使用-Winline选项确定函数未被内联以及原因。

  2. 当然,它将取决于编译器、版本、标志等。有时内联可能会适得其反(代码膨胀等),因此每个编译器都有自己的规则来决定是否应该内联函数。顺便说一下,inline关键字只是一个提示,一些库如eigen很难强制执行内联。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接