std::function lambda 优化

std::function lambda optimization

std::function 已知存在性能问题,因为它可能会进行堆分配。承认,如果您是 100% 诚实的,那么在大多数情况下,一次堆分配应该几乎不是问题……但我们假设在特定情况下进行堆分配是不可取的或禁止的。也许我们正在进行几百万次回调,但不希望为此分配几百万次堆分配,无论如何。

所以...我们想避免这种堆分配。

Dobbs 博士的文章 Efficient Use of Lambda Expressions and std::function 给出了优化 std::function 使用的建议,方法是利用标准推荐并在每个主流标准库中实现的小对象优化。

这篇文章详细解释了标准库必须如何复制仿函数,因为 std::function 对象可能比原始仿函数长寿(尽管您可以使用 std::ref 如果您确定它不会), 这将是糟糕的魔力。此外,需要复制捕获,这就是问题所在:事先不知道闭包的确切类型(或其大小),因为它可能是具有任意数量捕获的任何类型的闭包,因此必须做出一些妥协。达到一定大小,捕获将保存在 function 对象内的存储中,超过该大小,它将被动态分配。存储很小,从 12 到 16 字节不等,因此假设 64 位构建,最多两个指针(不计算实际函数指针)。

博士。因此,Dobbs 建议(其他几个站点采纳了该建议,似乎没有太多反对意见)捕获对结构的引用,该结构包含对您实际想要捕获的内容的引用。这样一来,您只捕获一个引用,这非常完美,因为它总是适合小型对象存储。

这是如何运作的?首先需要复制内容的假设是 function 对象 可能比原始闭包 的范围更长。这意味着,当然,它 比它所引用的结构以及从该结构内部引用的任何内容都长。

这应该如何运作?既然我看不出它是如何工作的,有没有更广为人知的方法来解决这个问题? (不引用无效对象的对象)

我认为如果函数对象的寿命超过其调用函数(并且您正在捕获对堆栈上对象的引用),它就不会起作用。

在许多实际情况下,函数对象是在本地使用的,不会比调用者活得更久,这样您就可以避免堆分配(但话又说回来,编译器可能会优化引用和整个 struct 技术可能不是必需的)。

这是一个简单的 test,它编译但崩溃(在 C++14 模式下在 clang 上测试。)