为什么将影响 lambda 的代码编译为 std::function 如此缓慢,尤其是使用 Clang?

问题描述 投票:0回答:3

我发现相对少量代码的编译时间,将 lambda 函数转换为

std::function<>
值,可能会非常长,尤其是使用 Clang 编译器时。

考虑以下创建 100 个 lambda 函数的虚拟代码:

#if MODE==1
#include <functional>
using LambdaType = std::function<int()>;
#elif MODE==2
using LambdaType = int(*)();
#elif MODE==3
#include "function.h" // https://github.com/skarupke/std_function
using LambdaType = func::function<int()>;
#endif

static int total=0;

void add(LambdaType lambda)
{
    total += lambda();
}

int main(int argc, const char* argv[])
{
    add([]{ return 1; });
    add([]{ return 2; });
    add([]{ return 3; });
    // 96 more such lines...
    add([]{ return 100; });

    return total == 5050 ? 0 : 1;
}

根据

MODE
预处理器宏,该代码可以选择以下三种方式将lambda函数传递给
add
函数:

  1. std::function<>
    模板类
  2. 一个简单的 C 函数指针(这里可能只是因为没有捕获)
  3. 由 Malte Skarupke 编写的
    std::function
    的快速替代品(https://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction/

无论何种模式,程序总是以常规

0
错误代码退出。 但是现在看看 Clang 的编译时间:

$ time clang++ -c -std=c++11 -DMODE=1 lambdas.cpp 
real    0m8.162s
user    0m7.828s
sys 0m0.318s

$ time clang++ -c -std=c++11 -DMODE=2 lambdas.cpp 
real    0m0.109s
user    0m0.056s
sys 0m0.046s

$ time clang++ -c -std=c++11 -DMODE=3 lambdas.cpp 
real    0m0.870s
user    0m0.814s
sys 0m0.051s

$ clang++ --version
Apple LLVM version 10.0.0 (clang-1000.11.45.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

哇。

std::function
和指向函数模式的指针之间有 80 倍的编译时间差异!
std::function
和它的替代品之间甚至相差 10 倍。

怎么可能?是否存在特定于 Clang 的性能问题,还是由于

std::function
要求的固有复杂性?

我尝试用 GCC 5.4 和 Visual Studio 2015 编译相同的代码。编译时间也有很大差异,但没有那么大。

海湾合作委员会

$ time g++ -c -std=c++11 -DMODE=1 lambdas.cpp 
real    0m1.179s
user    0m1.080s
sys 0m0.092s

$ time g++ -c -std=c++11 -DMODE=2 lambdas.cpp 
real    0m0.136s
user    0m0.120s
sys 0m0.012s

$ time g++ -c -std=c++11 -DMODE=3 lambdas.cpp 
real    0m1.994s
user    0m1.792s
sys 0m0.196s

视觉工作室

C:\>ptime cl /c /DMODE=1 /EHsc /nologo lambdas.cpp
Execution time: 2.411 s

C:\>ptime cl /c /DMODE=2 /EHsc /nologo lambdas.cpp
Execution time: 0.270 s

C:\>ptime cl /c /DMODE=3 /EHsc /nologo lambdas.cpp
Execution time: 1.122 s

我现在正在考虑使用 Malte Skarupke 的实现,以提高运行时性能和大大增强编译时间。

c++ c++11 lambda clang std-function
3个回答
0
投票

使用 --save-temps 选项查看编译器在每种情况下必须处理的内容。 在我的 clang 6.0.1 机器上,MODE=1 生成了一个 575K 的预处理文件,因为包含了大量的标准库头文件。 MODE=1 生成一个 416 byte 文件,小 1000 倍。 生成的程序集也相差 10 倍。


0
投票

我没有能力测试和解释你的例子,但是,从 Clang 9.0.0 开始,它有能力对你的编译进行时间跟踪。有关更多信息的印象和链接,请参阅phoronix 文章。简而言之,您可以获得它正在执行的操作的 json,您可以通过在命令行中添加

-ftime-trace
来在漂亮的图形中可视化。

如果你注意到一些非常奇怪的事情,你总是可以在 bugs.llvm.org 上记录一个错误并有一个很好的再现(我认为改变这个问题的一些措辞会很好)

让我也添加一个关于测试代码的小评论。

std:: function
编译速度较慢,我并不感到惊讶,因为这需要额外的包含来解析。 (而且标准库包含的内容很大)同样对于运行时,缓慢的效果是合乎逻辑的,因为
std:: function
添加了很多无法优化的额外间接。

我建议添加一个第 4 年的案例,其中 add 是一个模板并且函数类型是模板参数:

template<typename LambdaType>
void add(LambdaType &&lambda)
{
    total += lambda();
}

0
投票

我也遇到过类似的事情,但与内存使用有关:

我有一个 RTTI 库,它在 lambda 中包装了很多类型相关的函数(比如构造函数和析构函数),并将它们存储在 std::functions 中。 由于反射器针对使用中的每种类型进行实例化,因此它具有巨大的 RAM 占用空间(需要大约 80 GB 的内存来只是编译)。

经过大量思考和搜索自我造成的病态元编程,我将问题缩小到 std::function,并且能够将 RAM 使用量从 80 GB 降低到 4 GB,只需使用原始函数指针和+ lambda 技巧。

RAM 开销似乎在我目前使用的所有编译器中都是一致的:

  • Clang 15.0.1(MSVC 的 clang-cl 变体)
  • MSVC 19.35.32215.0

我的猜测是所有 std::function 实现都涉及一些基本的元编程错误。

© www.soinside.com 2019 - 2024. All rights reserved.