使用std :: function会增加开销

4

我看到很多人建议不要使用std::function<>,因为它是一个较重量级的机制。有人能否解释一下这是为什么?


有什么替代方案吗?我问这个问题是因为在某些情况下没有其他选择。 - 101010
可能是std::function的性能开销是多少?的重复问题。 - underscore_d
5个回答

10

std::function是一种类型擦除的类。

它会保留以下内容,但擦除其余所有内容:

  • 使用所需签名进行调用(可以进行隐式转换)
  • 销毁
  • 复制
  • 精确转换回原始类型

可能还包括:

  • 移动

这会导致一些开销。一个典型的高质量的std::function将具有小对象优化(例如小字符串优化),当使用的内存量较小时,避免了堆分配。

函数指针也可以适应其中。

然而,仍然存在一些开销。如果使用兼容的函数指针初始化std::function,而不是直接调用相关的函数指针,则会进行虚函数表查找或调用其他函数,然后再调用函数指针。

在vtable的实现中,这是可能会引起缓存未命中、指令缓存未命中,然后又一次指令缓存未命中。对于函数指针,指针可能存储在本地,并且直接调用它,只引起一次指令缓存未命中。

此外,在实践中编译器对函数指针的理解比std::function更好:一些编译器可以在内联或整个程序优化期间判断出指针是常量值。我从未看到过一个能够处理std::function的。

对于较大的对象(例如在某个实现中大于sizeof(std::string)),std::function还会进行堆分配。这是另一种开销。对于函数指针和引用包装器,标准保证采用SOO。


直接存储lambda而不将其存储在std::function中甚至比函数指针更好:在这种情况下,正在运行的代码隐含在lambda类型中。这使得代码可以轻松确定它被调用时会发生什么,并且易于编译器进行内联。

只有在需要时才进行类型擦除。


5
函数指针和引用包装器(reference_wrapper)都保证使用小对象优化。 - T.C.
@T.C. std::function 是否提到了对函数的引用?我在_cppreference_上没有看到任何相关信息(显然这并不是决定性的)。快速测试表明它可以从一个函数引用构造,并且可以正常工作,但我不知道这是否证明了什么。该引用是否只转换为指针?无论如何,我都希望能在某个地方提到引用的情况。 - underscore_d
1
@underscore_d 构造函数采用值传递,因此它总是被转换。 - T.C.

2

在内部,std::function 通常使用类型擦除(一种实现方式的简化解释可以在这里找到)。存储您的函数对象在 std::function 对象内可能会涉及堆分配的成本。调用您的函数对象的成本通常是通过指针间接引用再加上虚函数调用。此外,虽然编译器正在改进,但虚函数调用通常抑制了您的函数的内联。

尽管如此,我建议使用 std::function,除非您通过测量知道成本太高(通常在无法承受堆分配、需要非常低延迟的地方多次调用您的函数等情况下),因为编写直截了当的代码比过早地进行优化更好。


1

根据实现方式不同,std::function 由于使用类型擦除会增加一些开销。还有其他实现方式,例如 Don Clugston 的快速委托,在 C++11 中实现 这里。请注意,它使用 UB 来创建最快的委托,但仍然非常可移植。


1
如果您想要类型擦除,它是正确的工具,并且几乎肯定不是瓶颈,也不是您可以更快地编写的内容。
然而,有时候使用类型擦除可能会很诱人,但实际上并不需要。这就是需要划清界限的地方。例如,如果您只想在本地保留一个lambda,则可能不是正确的工具,您应该只使用:
auto l = [](){};

同样对于你不打算类型抹除的函数指针 - 只需使用函数指针类型即可。
您也不需要对来自<algorithm>或自己的等效模板进行类型抹除,因为没有必要让异质函数对象类型共存。

0

这并不是这样。

简单来说,除非你对程序进行了分析并证明它太重,否则它并不会太重。显然你没有这样做(否则你就会知道这个问题的答案),所以我们可以安全地得出结论,它实际上并不重。

在得出它运行速度太慢的结论之前,你应该始终进行分析。


2
这不是一个有用的答案。OP正在询问为什么会出现这种情况,无论进行多少次分析,都无法回答这样的问题。而且你声称因为OP没有进行分析,所以它并不太重,这是一个非正常推理。这只是一个高马,你爬上去只是为了用霍尔定律打击别人。 - Timtro

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