【问题标题】:Execution time of c++ code on Linux for the first time is extremly slow第一次在Linux上c++代码执行时间极慢
【发布时间】:2019-02-21 09:19:41
【问题描述】:

相信很多人都经历过。在 linux 上第一次执行 c++ 代码总是需要更长的时间。

就像在我的 linux 机器上第一次调用 ::clock_gettime(CLOCK_REALTIME, &ts); 比第三次慢五倍左右。

第一次分配内存比第二次慢 100 倍。

我尝试了预分配并在我的应用程序中使用了mlockall,但即便如此,一个函数的第一次执行比第二个慢大约 160 倍,比第三个慢大约两倍。

函数的伪代码如下。 msg 在堆上分配。但它不包括在时间测量中。 msg2 是 POD,所以在 slow_for_the_first_time 中根本没有内存分配。

void slow_for_the_first_time(Message * msg) {
     Msg2 msg2;
     //set msg2 using msg
  .... }

只是想知道,什么可能导致第一次执行缓慢?有没有办法避免?

erenon 的回答很有帮助。我认为这可能是因为 Msg2 是在 so 库中定义的。

在使用 LD_BIND_NOW=1 之前,第一次执行时间约为 8000 纳秒,第二次约为 500 纳秒,第三次约为 200 纳秒。

现在第一个执行时间约为 2000 纳秒,而第二个和第三个保持不变。所以还是比第三次执行慢了10倍,应该还有其他因素影响第一次执行时间。

一些有趣的发现。

slow_for_the_first_time之前调用下面的方法可以减少第一次执行时间1微秒

void dummySet(Msg2& msg2)
{
    //set all fields of msg2. msg2 has about 30 fields it won't work if only set one field of msg2.
}

还有一点值得一提的是,第一次执行的慢肯定和msg无关,就像下面代码中的第二次slow_for_the_first_time

char buffer[sizeof(Message)];
memset(buffer, 0, sizeof(buffer));
slow_for_the_first_time((Message*)buffer);//calling the method with a dummy buffer.
.....
slow_for_the_first_time(msg);//calling the method for the second time with a real msg.

与下面代码中的第二个slow_for_the_first_time 一样快

slow_for_the_first_time(msg);//the first time takes around 2000 nanoseconds
.....
slow_for_the_first_time(msg);//the second time takes around 500 nanoseconds.

【问题讨论】:

    标签: c++ linux


    【解决方案1】:

    第一次引用动态链接的符号时,需要在动态加载的符号集中查找它们。要查看这是否真的是问题,请执行以下操作:

    $ LD_BIND_NOW=1 ./your_program
    

    LD_BIND_NOW 将指示链接器修复 GOT 和 PLT 中的每个条目的地址:这会使启动稍微慢一些,但也可能解决“第一次调用很慢”的问题。

    如果这被证明是问题所在,您可以尝试静态链接库或预链接。

    【讨论】:

    • 这个选项有很大帮助,现在第一次执行时间从大约 8000 纳秒下降到大约 2000 纳秒。虽然它仍然比第三次慢 10 倍。
    【解决方案2】:

    除了 erenon 谈到 in their answer 的惰性链接之外,还有两个导致首次运行缓慢的因素:冷缓存和冷分支预测。

    整体而言,后续调用的加速来自:

    • 外部符号:一旦符号被链接器解析,它就在程序的整个生命周期内,在此之后实际上是一个空操作;
    • 数据:当 CPU 处理数据时,它会临时存储在 CPU 缓存中。将内存加载到该缓存中是一项昂贵的操作。但是一旦它在那里,由于缓存是一个非常接近 CPU 的超快内存,相同的数据可以很快地用于下一次。你可以阅读这个other answer about cache
    • CPU分支预测通过尝试和预测代码分支的方式显着提高了代码执行。这也需要热身。这里是an excellent answer about branch prediction

    总的来说,代码在第一次执行时往往很慢。如果这是一个问题,解决方案是:

    • LD_BIND_NOW,启动时链接;
    • 缓存预热;
    • 分支预测预热。

    【讨论】:

    • 通过预热,你的意思是我必须在真正需要使用之前运行代码。就像把它称为虚拟工作等。
    • 这是一种缓存和分支预测预热技术,是的。它有效。
    • 另一件事是我不认为缓慢是由缓存引起的。请检查我的最后发现,缓慢不是由味精本身引起的。并且 msg2 在堆栈上。它也与分支预测无关。由于函数中没有分支,只是设置了 Msg2 的字段。
    • 我可能对数据过于狭隘了。指令也是数据。所以它可能仍然与缓存有关。也许使用虚拟缓冲区调用 slow_for_the_first_time 有助于将指令加载到缓存中?
    • @LeiYu 这是一个通用的答案。它为您提供有关可能原因的提示。对于您的具体情况,只有一个答案:尝试并衡量。你无法猜测任何关于性能的事情。
    猜你喜欢
    • 2020-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多