c++ 为什么将影响lambda的代码编译为std::function的速度如此之慢,特别是使用Clang?

mhd8tkvw  于 2023-05-02  发布在  其他
关注(0)|答案(3)|浏览(151)

我发现相对少量的代码的编译时间,将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<>模板类
    1.一个简单的C函数指针(这里可能只是因为没有捕获)
    1.由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

Whow. std::function和指向函数模式的指针之间有80倍的编译时间差!甚至std::function和它的替代品之间有10倍的差异。
怎么会呢?是Clang特有的性能问题,还是由于std::function需求的固有复杂性?
我尝试用GCC 5编译相同的代码。4和Visual Studio 2015。也有很大的编译时间差异,但没有那么多。

GCC

$ 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

Visual Studio

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的实现,以获得更好的运行时性能和更大的编译时间增强。

uxh89sit

uxh89sit1#

看看编译器在每种情况下使用--save-temps选项必须处理什么。在我的机器上用clang 6.0.1时,MODE=1会生成一个575 K的预处理文件,这是由于包含了大量的标准库头文件。MODE=1生成一个416 byte 的文件,小1000倍。生成的组件也相差10倍。

dkqlctbz

dkqlctbz2#

我没有能力测试和解释你的例子,但是,从Clang 9。0.0上,它能够对编译进行时间跟踪。请参阅phoronix文章了解更多信息。简而言之,您可以通过将-ftime-trace添加到命令行来获得它正在做什么的json,您可以在一个漂亮的图形中可视化它。
如果你发现了一些非常奇怪的东西,你可以随时在www.example上记录一个bug www.example.com 与一个很好的复制(我认为改变一些措辞的这个问题将是罚款)
让我也添加一个关于测试代码的小注解。std:: function的编译速度较慢,我并不感到惊讶,因为这需要额外的include来解析。(标准库包含的内容非常庞大)同样对于运行时来说,缓慢的影响是合乎逻辑的,因为std:: function增加了许多无法优化的额外间接。
我建议添加一个第四年的case,其中add是一个模板,函数类型是模板参数:

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

5lhxktic3#

我也遇到了类似的情况,但与RAM使用有关:
我有一个RTTI library,它在lambdas中 Package 了很多类型相关的函数(如构造函数和析构函数),并将它们存储在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实现都涉及到一些基本的元编程问题。

相关问题