通过 valgrind 和 perf/FlameGraphs,我确定了我的应用程序的一部分几乎消耗了 100% 的 CPU:
for(size_t i = 0; i < objects.size(); i++) {
//this part consumes 11% CPU ----->
collions_count = database->get_collisions(collisions_block, objects[i].getKey());
feature1 = objects[i].feature1;
//<--------
for(int j = 0; j < collions_count * 2; j += 2) {
hash =
((collisions_block[j] & config::MASK_1) << config::SHIFT) |
((collisions_block[j+1] - feature1) & config::MASK_2);
if (++offsets[hash] >= config::THRESHOLD_1) {
//... this part consumes < 1% of CPU
}
}
}
hash 的计算和后面的 if 语句占用了所有应用程序近 90% 的 CPU。
-
collisions_block
初始化一次,类型为int[100000]
-
config::
是一个命名空间,变量包含全局配置 -
offsets
初始化一次,类型为uint8_t[1<<24]
- 我正在运行 Centos7 Linux 3.10.0-327.13.1.el7.x86_64
- 所有 CPU 都用于
usr
没有iowait
在 mpstat 输出中 - 我正在使用 g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4) 和标记
-std=gnu++11 -Ofast -Wall
进行编译
有什么方法可以加快内循环?
最佳答案
我发现性能瓶颈是对数组 ++offsets[hash]
的无序访问.它消耗了大部分 CPU 时间 (75+%)。通过从 1<<24
减小数组的大小,我实现了 2.5 倍的速度提升。至 1<<21
并尝试适当的 MASKS
配置。
我将简要描述我是如何发现问题的
for(size_t i = 0; i < objects.size(); i++) {
//this part consumes 11% CPU ----->
collions_count = database->get_collisions(collisions_block, objects[i].getKey());
feature1 = objects[i].feature1;
//<--------
for(int j = 0; j < collions_count * 2; j += 2) {
hash = calculate_hash(collisions_block[j],
collisions_block[j+1],
feature1,
config::MASK_1,
config::MASK_2
config::SHIFT);
if (check_condition(hash, config::THRESHOLD_1)) {
//... this part consumes < 1% of CPU
}
}
}
- 拆分关键的 2 行以分隔函数以便更好地分析(小心放置
__attribute__((noinline))
以防止 gcc 内联新函数。如果内联它们将不会出现在调用堆栈中) - 用
-g -rdynamic
编译代码gcc 标志 - 运行采样性能工具
perf record -p <pid> -F 200 -g --call-graph dwarf -- sleep 60
- 转换为 FlameGraph为了更好的可读性
perf script | ./stackcollapse-perf.pl > out.perf-folded && ./flamegraph.pl out.perf-folded > graph.svg
- 从火焰图中识别最昂贵的函数并对其进行优化
关于c++优化2行关键代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37881307/