第一次在 Linux 上执行 c++ 代码的时间非常慢

Execution time of c++ code on Linux for the first time is extremly slow

本文关键字:时间 非常 代码 c++ Linux 执行 第一次      更新时间:2023-10-16

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

就像第一次打电话::clock_gettime(CLOCK_REALTIME, &ts);比在我的 linux 盒子上第三次慢五倍。

第一次分配记忆比第二次慢 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
.... }

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

埃雷农的回答有很大帮助。我认为这可能是因为 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.

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

$ LD_BIND_NOW=1 ./your_program

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

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

除了 erenon 在他们的答案中谈到的惰性链接之外,还有另外两个因素导致首次运行时执行缓慢:冷缓存和冷分支预测。

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

  • 外部符号:一旦链接器解析了一个符号,它就是程序的生命周期,之后实际上是无操作的;
  • data:当数据被CPU处理时,它暂时存储在CPU缓存中。将内存加载到该缓存中是一项成本高昂的操作。但是一旦它进入那里,由于缓存是一个非常靠近CPU的超快内存,因此下次可以快速获得相同的数据。您可以阅读有关缓存的其他答案。
  • CPU:分支预测通过尝试和预测代码分支的方式,显著改进了代码执行。这也需要热身。这是关于分支预测的一个很好的答案。

总体而言,代码在首次执行时往往很慢。如果这是一个问题,解决方案是:

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