4

我的代码中有一个循环迭代 1 亿次(模拟模型的 1 亿次复制需要)。myarray对于 1 亿次迭代中的每一次,我通过索引一个名为 的整数变量从数组 ( ) 中检索一个值age。由于数组的长度,它只对索引myarray[age]有效age=0,...,99。但是, 的实际域age0,...,inf.

所以,我有以下功能

int tidx(const int& a) {
    return std::min(a,99);
}

这允许索引myarray[tidx(age)]

我怎样才能更有效地做到这一点?

[下面的性能输出]

构建说明我正在使用的编译器标志的源文件的示例:

Building file: ../SAR.cpp
Invoking: GCC C++ Compiler
g++ -O3 -Wall -c -fmessage-length=0 -Wno-sign-compare -fopenmp -MMD -MP -MF"SAR.d" -MT"SAR.d" -o"SAR.o" "../SAR.cpp"
Finished building: ../SAR.cpp

perf record后跟perf report

Samples: 280  of event 'cycles', Event count (approx.): 179855989                                                                                   
 24.78%  pc2  libc-2.17.so         [.] __GI_____strtod_l_internal
 11.35%  pc2  pc2                  [.] samplePSA(int, double, int, NRRan&)
  6.67%  pc2  libc-2.17.so         [.] str_to_mpn.isra.0
  6.15%  pc2  pc2                  [.] simulate4_NEJMdisutilities(Policy&, bool)
  5.68%  pc2  pc2                  [.] (anonymous namespace)::stateTransition(double const&, int const&, int&, double const&, bool const&, bool&, bo
  5.25%  pc2  pc2                  [.] HistogramAges::add(double const&)
  3.73%  pc2  libstdc++.so.6.0.17  [.] std::istream::getline(char*, long, char)
  3.02%  pc2  libstdc++.so.6.0.17  [.] std::basic_istream<char, std::char_traits<char> >& std::operator>><char, std::char_traits<char> >(std::basic_
  2.49%  pc2  [kernel.kallsyms]    [k] 0xffffffff81043e6a
  2.29%  pc2  libc-2.17.so         [.] __strlen_sse2
  2.00%  pc2  libc-2.17.so         [.] __mpn_lshift
  1.72%  pc2  libstdc++.so.6.0.17  [.] __cxxabiv1::__vmi_class_type_info::__do_dyncast(long, __cxxabiv1::__class_type_info::__sub_kind, __cxxabiv1::
  1.71%  pc2  libc-2.17.so         [.] __memcpy_ssse3_back
  1.67%  pc2  libstdc++.so.6.0.17  [.] std::locale::~locale()
  1.65%  pc2  libc-2.17.so         [.] __mpn_construct_double
  1.38%  pc2  libc-2.17.so         [.] memchr
  1.29%  pc2  pc2                  [.] (anonymous namespace)::readTransitionMatrix(double*, std::string)
  1.27%  pc2  libstdc++.so.6.0.17  [.] std::string::_M_mutate(unsigned long, unsigned long, unsigned long)
  1.15%  pc2  libc-2.17.so         [.] round_and_return
  1.02%  pc2  libc-2.17.so         [.] __mpn_mul
  1.01%  pc2  libstdc++.so.6.0.17  [.] std::istream::sentry::sentry(std::istream&, bool)
  1.00%  pc2  libc-2.17.so         [.] __memcpy_sse2
  0.85%  pc2  libstdc++.so.6.0.17  [.] std::locale::locale(std::locale const&)
  0.85%  pc2  libstdc++.so.6.0.17  [.] std::string::_M_replace_safe(unsigned long, unsigned long, char const*, unsigned long)
  0.83%  pc2  libstdc++.so.6.0.17  [.] std::locale::locale()
  0.73%  pc2  libc-2.17.so         [.] __mpn_mul_1

来自perf stat

 Performance counter stats for './release/pc2':

         62.449034 task-clock                #    0.988 CPUs utilized          
                49 context-switches          #    0.785 K/sec                  
                 3 cpu-migrations            #    0.048 K/sec                  
               861 page-faults               #    0.014 M/sec                  
       179,240,478 cycles                    #    2.870 GHz                    
        58,909,298 stalled-cycles-frontend   #   32.87% frontend cycles idle   
   <not supported> stalled-cycles-backend  
       320,437,960 instructions              #    1.79  insns per cycle        
                                             #    0.18  stalled cycles per insn
        70,932,710 branches                  # 1135.850 M/sec                  
           697,468 branch-misses             #    0.98% of all branches        

       0.063228446 seconds time elapsed

我将不胜感激任何评论。我需要学习如何解释/阅读这些信息,因此任何可能帮助我入门的提示将不胜感激。

4

5 回答 5

10

为了优化代码,首先必须弄清楚哪里是瓶颈。要找到瓶颈,必须对代码进行分析。否则变化是大量时间将浪费在根本不重要的微/错误优化上。

我没有在您的最小工作代码示例(您没有提供)上使用探查器,但根据我的经验,我可以告诉您——您的tidx()函数不是瓶颈,您不应该关心 std::min()in这个案例。瓶颈更有可能是内存访问和停滞的 CPU 周期。

对于初学者,如果可能的话,尝试展开你的循环(如果编译器没有为你这样做)。执行 25000000 次迭代可能比 100000000 次更有效,但在单个循环周期中执行更多操作。但在你这样做之前,你必须确保展开循环是有帮助的,而不是有害的。这通常是通过分析来完成的,所以我们回到这一点,为了优化代码,首先必须弄清楚哪里是瓶颈。找到一个瓶颈......哦,等等,我几乎进入了不定式循环。中止。

于 2013-05-24T18:03:03.543 回答
3

很多人犯的第一个错误是盯着代码看,专注于某事或其他事情,想知道他们是否可以让它更快。

第二个错误是在gprof上面跑,希望找到一个“瓶颈”。

唯一gprof可以可靠找到的有用的东西是您的代码是否受 CPU 限制,并且它在您编译的代码中受 CPU 限制。它不善于发现可以消除的函数调用问题。它不善于发现你不知道自己在做的 I/O 问题。

很多人都使用这种方法这就是它起作用的原因

于 2013-05-24T19:18:12.967 回答
2

请记住在启用优化的情况下分析可执行文件。对于性能测试来说,运行非优化的可执行文件是没有用的,因为它将具有完全不同的性能特征。

然后考虑你可以做些什么来避免这么多的查找。做更少的工作(算法改进)将花费更少的时间。

还要编写没有“过早悲观”的代码,正如 Herb Sutter 喜欢这样称呼它。

定义:过早的悲观是当你编写的代码比它需要的要慢时,通常是通过要求不必要的额外工作,而同等复杂的代码会更快并且应该自然地从你的手指中流露出来。

例如,内联可能有帮助,也可能没有帮助,但您可能希望编写代码以免排除内联。稍后,您可以强制或阻止内联,并比较哪些对您的执行环境更好。您也可能不想使用对像 int 这样的小类型的引用。如果没有优化,引用参数可能会用一个指针来实现,现在它通常比 int 更大更慢。即使在 32 位硬件上,引用也不会比 int 快。

int tidx(int a) {
    return std::min(a,99);
}

然后你可以尝试一些其他的优化技术;独立任务是否并行运行?您的数据结构是否具有良好的参考局部性特征?如果事情并行运行,您是否受到虚假共享的影响?您可以使用 SIMD 或其他数据并行化吗?您还可以使用编译器设置或在代码的特定部分启用特定优化。这就是测试性能变得非常重要的地方,因为不同的硬件可以有完全不同的行为。此外,由于大多数此类优化会混淆代码,因此您不想为很少或没有任何回报而付出这个代价。

于 2013-05-24T19:14:13.497 回答
2

已经给出了关于分析的好建议。

min<T>(T x, T y)应该相当于return (x < y)?x:y;

作为汇编程序,这将变为:

mov    x, %eax
cmp    y, %eax
cmovg  y, %eax

或类似的规定。如果您在 gcc 中启用 -O2,这三个指令绝对应该内联。

于 2013-05-24T18:46:50.343 回答
2

有几个想法可以研究

  • 检查为函数生成的程序集。它可能会或可能不会编译成次优的东西
  • 调用该函数的次数更少
  • 重写函数以使用 SSE 指令一次计算 4 个(或 8 个使用 AVX)值的最小值
  • 重构函数的调用方式。理想情况下,调用之间的距离不应太远,以至于代码会从指令缓存中清除。
  • 不要通过 const 引用传递 int 。那有什么意义呢?只需传递 int 的副本。
  • 检查是否有任何别名或虚假共享可能会减慢您在呼叫站点的速度。

但实际上,这是一个极其简单的功能。仅仅通过查看它的实现并没有太多收获。我的大部分建议都涉及到函数的调用方式、调用位置、调用时间以及调用数据。这些可能是您需要查看的内容。

于 2013-05-24T18:58:37.690 回答