你的 C++ 代码运行缓慢?是时候深入探究了
\n\n你编写了一段乍看之下完美的 C++ 代码。编译无误,程序启动... 但运行起来却有点迟缓。熟悉吗?你恨不得立刻用汇编重写所有代码,或者赶紧添加多线程。别着急。问题很可能不在于架构,而在于几个消耗了 90% CPU 时间的“热点”函数。
\n\n在 Linux 世界中,我们拥有强大的工具库用于 C++ 代码性能分析。这不是魔法,而是科学。我们不靠猜测,而是靠测量。在本指南中,我将向你展示四种实用的方法:从快速的“宏观概览”到微观基准测试。你将学会如何像真正的专家一样在 Linux 上分析 C++ 代码。
\n\n为什么猜测是邪恶的,而性能分析是好的
\n\n在深入代码之前,请记住优化的黄金法则:“不要优化未经测量的代码”。你的直觉是一个糟糕的顾问。我们常常花费数小时优化一个每分钟才调用一次的函数,却忽略了一个简单的千万次迭代循环。
\n\n性能分析回答了三个主要问题:
\n- \n
- 程序在哪里花费了最多时间? \n
- 为什么会发生这种情况(算法效率低、大量 I/O 操作、缓存未命中)? \n
- 你的修改改变了什么? \n
今天,我们将介绍四种工具,它们能覆盖你 99% 的需求。开始吧!
\n\n工具 #1:gprof — 经典之作
\n\ngprof 是一个老牌、可靠的内置 GNU 性能分析器。它分两步工作:在程序运行时收集统计数据,然后进行分析。优点:无需安装额外软件,每个发行版都自带。缺点:结果近似,并且不能直接用于多线程程序。
\n\n如何使用 gprof
\n\n步骤 1. 使用特殊标志 -pg 编译程序:
g++ -pg -O2 my_program.cpp -o my_program\n\n注意:我们使用了 -O2。对未优化的调试版本进行性能分析是没有意义的,你会得到失真的结果。
步骤 2. 运行程序。由于 gprof 在收集数据,程序运行会稍慢一些。
\n./my_program\n\n步骤 3. 在当前文件夹中会生成一个 gmon.out 文件。分析它:
gprof my_program gmon.out > analysis.txt\n\n打开 analysis.txt,你会看到类似这样的内容:
Flat profile:\nEach sample counts as 0.01 seconds.\n % cumulative self self total\n time seconds seconds calls us/call us/call name\n 60.00 0.06 0.06 10000 6.00 6.00 slow_function\n 20.00 0.08 0.02 5000 4.00 4.00 fast_function\n 10.00 0.09 0.01 main\n\n瞧!我们看到 slow_function 占用了 60% 的时间。这就是我们需要优化的候选函数。
用于性能分析的代码示例
\n\n让我们编写一个简单的程序,我们将用所有工具对其进行分析:
\n\n#include <iostream>\n#include <cmath>\n\nvoid slow_function(int n) {\n volatile double sum = 0.0; // volatile 防止编译器优化为零\n for (int i = 0; i < n; ++i) {\n for (int j = 0; j < 1000; ++j) {\n sum += std::sin(i) * std::cos(j);\n }\n }\n std::cout << sum << std::endl;\n}\n\nvoid fast_function(int n) {\n volatile int count = 0;\n for (int i = 0; i < n; ++i) {\n count += i % 2;\n }\n std::cout << count << std::endl;\n}\n\nint main() {\n slow_function(10000);\n fast_function(50000);\n return 0;\n}\n\n使用 -pg 标志编译并运行