【问题标题】:c++ optimization of 2 lines of critical code2行关键代码的c++优化
【发布时间】:2016-06-24 21:38:45
【问题描述】:

通过 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&lt;&lt;24] 类型
  • 我正在运行 Centos7 Linux 3.10.0-327.13.1.el7.x86_64
  • 所有 CPU 都用于 usr mpstat 输出中没有 iowait
  • 我正在使用 g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4) 和标志 -std=gnu++11 -Ofast -Wall 进行编译

有什么办法可以加快内循环?

【问题讨论】:

  • 另外,offsets 是一个用hash 访问的巨大数组,我认为它根本没有顺序值。这意味着缓存可能不适用于该数组,这会使循环变慢。虽然不确定解决方法(也许使用稀疏数据结构,例如“默认地图”?)
  • @jdehesa 由于哈希的特性,我认为不可能有解决方法......
  • 我看到有两件事情可以尝试,但它们的作用不大。 1) 计算“const int n_collisions = collions_count * 2;”在循环之前,将循环更改为“for(int j = 0; j config::THRESHOLD_1 不是const,则在循环之前制作const 的副本。编译器可能可以在这里进行更多优化。其他两个 configs 同上。我怀疑这其中的任何一项都算不了什么,但值得一试。
  • 预计算 collions_count * 2 在 -O1 之后不会有任何改进。除非您更改循环内的值,否则编译器应自行预先计算。尝试使用 -flto 编译和链接,有时它可以启用 大量 优化。这取决于程序,但通常我发现-ftlo-O3 更重要。
  • 尝试不同的优化选项。有时-Os 可能比-O[123] 快,有时-O2-O3-Ofast 快——这完全取决于要优化的代码——有时对不同的文件使用不同的优化级别是有意义的。也可以试试 LTO。

标签: c++ optimization


【解决方案1】:

我发现性能瓶颈是对数组++offsets[hash] 的无序访问。它消耗了大部分 CPU 时间 (75+%)。通过将数组的大小从 1&lt;&lt;24 减小到 1&lt;&lt;21 并尝试适当的 MASKS 配置,我实现了 2.5 倍的速度提升。

我将简要描述我是如何识别问题的

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

    }
  }
}
  1. 将关键的 2 行拆分为单独的函数,以便更好地进行分析(小心放置 __attribute__((noinline)) 以防止 gcc 内联新函数。如果内联,它们不会出现在调用堆栈上)
  2. 使用-g -rdynamic gcc 标志编译代码
  3. 运行采样性能工具perf record -p &lt;pid&gt; -F 200 -g --call-graph dwarf -- sleep 60
  4. 转换为FlameGraph 以获得更好的可读性perf script | ./stackcollapse-perf.pl &gt; out.perf-folded &amp;&amp; ./flamegraph.pl out.perf-folded &gt; graph.svg
  5. 从火焰图识别最昂贵的函数并优化它

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    • 2012-11-13
    • 1970-01-01
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多