绩效评估的惯用方法?

shp*_*ark 1 benchmarking microbenchmark

我正在评估我的项目的网络+渲染工作负载。

程序连续运行一个主循环:

while (true) {
   doSomething()
   drawSomething()
   doSomething2()
   sendSomething()
}
Run Code Online (Sandbox Code Playgroud)

主循环每秒运行 60 多次。

我想查看性能故障,每个程序需要多少时间。

我担心的是,如果我打印每个程序的每个入口和出口的时间间隔,

这会导致巨大的性能开销。

我很好奇什么是衡量性能的惯用方法。

日志打印是否足够好?

Pet*_*des 12

通常:对于重复的简短事物,您可以对整个重复循环进行计时。(但微基准测试很难;除非您了解这样做的含义,否则很容易扭曲结果。)

或者,如果您坚持对每个单独的迭代进行计时,请将结果记录在一个数组中并稍后打印;您不想在循环中调用重量级的打印代码。

这个问题太宽泛了,不能说更具体的东西。

许多语言都有基准测试包,可以帮助您编写单个函数的微基准测试。使用它们。例如,对于 Java,JMH 确保被测函数在执行定时运行之前由 JIT 和所有爵士乐进行预热和完全优化。并在指定的时间间隔内运行它,计算它完成的迭代次数。

当心常见的微基准测试陷阱:

  • 未能预热代码/数据缓存和其他内容:定时区域内的页面错误以接触新内存,或代码/数据缓存未命中,这不属于正常操作的一部分。(注意此效果的示例:性能:memset基于此错误错误结论示例)
  • 未能让 CPU 有时间加速到最大涡轮增压:现代 CPU 时钟降低到空闲速度以节省电量,仅在几毫秒后加速。(或更长,取决于操作系统/硬件)。

    相关:在现代 x86 上,RDTSC 计算参考周期,而不是核心时钟周期,因此它受到与挂钟时间相同的 CPU 频率变化影响。

  • 在乱序执行的现代 CPU 上,有些事情太短而无法真正有意义地计时,另请参阅一小块汇编语言(例如,由编译器为一个函数生成)的性能不能用单个数字来表征,即使它不分支或访问内存(因此不会发生错误预测或缓存未命中)。它从输入到输出有延迟,但如果使用独立输入重复运行,则不同的吞吐量会更高。例如,addSkylake CPU 上的一条指令有 4 个/时钟的吞吐量,但有 1 个周期的延迟。所以dummy = foo(x)可以比它快 4 倍x = foo(x);在一个循环中。浮点指令比整数具有更高的延迟,所以它通常是一个更大的交易。大多数 CPU 上的内存访问也是流水线式的,因此循环数组(下一次加载的地址很容易计算)通常比遍历链表快得多(下一次加载的地址在上一次加载完成之前不可用)。

    显然,CPU 之间的性能可能不同;从大局来看,A 版在 Intel 上更快,B 版在 AMD 上更快是罕见的,但在小规模上这很容易发生。在报告/记录基准数据时,请始终注意您测试的 CPU。

  • 与以上和以下几点相关:例如,您不能*在 C 中对运算符进行基准测试。它的一些用例的编译与其他用例非常不同,例如tmp = foo * i;在循环中通常会变成tmp += foo(强度降低),或者如果乘数是 2 的常数幂,编译器将只使用移位。源代码中的相同运算符可以编译为非常不同的指令,具体取决于周围的代码。
  • 需要在启用优化的情况下进行编译,但您还需要阻止编译器优化工作,或将其提升出循环。确保您使用结果(例如打印它或将其存储到 a volatile),以便编译器必须生成它。对输入使用随机数或其他东西而不是编译时常量,这样您的编译器就无法对实际用例中不是常量的事物进行常量传播。在 C 中,您有时可以使用内联 asm 或volatile为此使用,例如此问题所询问的内容。一个像Google Benchmark这样的好的基准测试包将包含这方面的功能。
  • 如果一个函数的实际用例让它内联到调用者中,其中一些输入是恒定的,或者操作可以优化到其他工作中,那么单独对其进行基准测试并不是很有用。
  • 对许多特殊情况进行特殊处理的大型复杂函数在重复运行时在微基准测试中看起来很快,尤其是每次使用相同的输入时。在现实生活中的用例中,分支预测通常不会为具有该输入的函数做好准备。此外,大规模展开的循环在微基准测试中看起来不错,但在现实生活中,它会因占用大量指令缓存而减慢其他一切,导致其他代码被逐出。

与最后一点相关:如果函数的实际用例包含大量小输入,则不要只针对大输入进行调整。例如memcpy,对于大量输入非常有用但需要很长时间才能确定对小输入使用哪种策略的实现可能并不好。这是一种权衡;确保它对于大输入足够好,但对于小输入也要保持低开销。

石蕊试纸:

  • 如果您在一个程序中对两个函数进行基准测试:如果颠倒测试顺序会改变结果,则您的基准测试是不公平的。例如,功能 A 可能看起来很慢,因为您首先对其进行了测试,而预热不足。示例:为什么 std::vector 比数组慢?(不是,无论哪个循环先运行,都必须为所有页面错误和缓存未命中付出代价;第二个循环只是放大填充相同的内存。)

  • 增加重复循环的迭代次数应该会线性增加总时间,并且不会影响计算的每次调用时间。如果不是,那么您有不可忽略的测量开销或您的代码被优化掉(例如,从循环中提升并且只运行一次而不是 N 次)。

即改变测试参数作为健全性检查。


对于 C/C++,另请参阅 Simple for() 循环基准测试与任何循环边界花费相同的时间,我在其中详细介绍了微基准测试和使用volatileasm停止使用 gcc/clang 进行优化的重要工作。


归档时间:

查看次数:

1322 次

最近记录:

5 年,4 月 前