gcc编译(有时)导致CPU负载过低。
gcc编译(有时)导致CPU负载过低。
我有一个较大的C++程序,它首先将数千个小文本文件读入内存,并在stl容器中存储数据。这个过程大约需要一分钟。定期地,编译会出现初始部分以大约22-23%的CPU负载运行的情况。一旦这个步骤完成,CPU负载就会回到约100%。开启O2标志时更容易发生,但并不一致。使用-p标志发生的频率更低,几乎不可能进行性能分析。我曾经捕获到一次,但是gprof的输出并没有帮助,所有的代码以相同的相对速度运行,只是在低CPU使用率下运行而已。\n我非常确定这与多核无关。我有一个四核CPU,大部分代码都是多线程的,但我测试了单线程运行时仍出现了这个问题。而且,当我在多个线程中运行有问题的步骤时,每个线程只占用约20%的CPU。\n对于问题的模糊性,我提前道歉,但我已经没有更多的想法来进一步排查,所以任何提示都可能有帮助。\n更新:只是为了确保清楚,代码的有问题部分有时候(约30-40%的编译)会以100%的CPU运行,所以很难相信(虽然合理)I/O是瓶颈。
gcc编译(有时)导致CPU负载不足的原因是:
1. 缓冲区缓存(buffer cache):编译过程中,系统会使用缓冲区缓存来提高效率,但有时会导致CPU负载不足。
解决方法:
可以通过向GCC传递以下标志来了解编译过程中发生了什么(例如,将其作为Makefile中的CXXFLAGS设置传递给g++):
- 使用-v参数让g++显示涉及的子进程(例如,适当的C++编译器cc1plus)。
- 使用-time参数让g++报告每个子进程的时间。
- 使用-ftime-report参数让g++(实际上是cc1plus)报告编译器内部阶段或通過的时间。
这样可以帮助定位编译过程中导致CPU负载不足的具体原因。
gcc编译(有时)导致CPU负载不足的问题出现的原因是程序在读取大量小文件时,大部分时间都被阻塞在等待磁盘I/O上。由于CPU在等待磁盘将数据传输给它的过程中并没有忙碌,所以CPU的负载显著低于100%。一旦磁盘I/O完成,现在就变成了CPU受限,程序将占用所有可用的CPU时间。
它有时运行更快的原因是因为(正如Jarryd和DigitalRoss提到的)一旦文件被读入系统内存中,它们就位于操作系统的缓存中,因此后续的加载速度将快上一个数量级,除非它们被其他磁盘I/O所替代。因此,如果你连续运行程序,第二次运行可能会快得多。如果你等待一段时间(并在此期间做其他事情),可能会有足够多的其他磁盘I/O将这些文件从缓存中逐出,这样再次读取它们将需要很长时间。
为了验证这个假设,可以将文件放在闪存驱动器上,观察问题是否改善。如果改善了,那就是时候考虑购买SSD了。
然后,在其他运行中,不会有延迟,因为这些文件现在在内存中,并且不需要再次从磁盘读取。
- 嗯,也许吧。如果它是一个快速的闪存驱动器,并且它连接在一个快速的总线上。我有一些旧的1G USB1.0闪存驱动器,它们比一个慢速的硬盘还要慢得多。
请参阅更新。我不明白为什么会有I/O瓶颈,因为有时它确实以100%的速度运行。
如果这些块已经从上一次运行中缓存,它将以100%的速度运行。
阅读我的先前评论。一旦数据被读入,I/O瓶颈不会有时发生,因为操作系统会保留硬盘数据的缓存。
问题的原因是Linux的缓冲区缓存机制。在编译大量文件时,由于文件需要从磁盘读取,CPU会大部分时间都在等待磁盘的旋转和寻道延迟。因此,报告的CPU使用时间会以一个低百分比的形式呈现,但实际上CPU的整体使用率可能会更高。
但是,一旦这些小文件被读入内存并完全缓存,后续运行中访问每个文件将成为纯粹的CPU密集型操作。
缓冲区的缓存是否保留取决于其他活动,例如重新编译。当运行新程序并读取其他文件时,这些程序和文件将被缓存,旧的缓存块将被清除,而且内存密集型的工作负载也会清除缓冲区缓存。
解决方法是在相同的编译运行之间清除缓冲区缓存。可以通过在每次运行之间执行以下命令来实现:
sync; echo 3 > /proc/sys/vm/drop_caches
可以在此处找到更详细的说明:go2linux.garron.me/linux/2011/01/…