绩效评估的惯用方式?

Posted

技术标签:

【中文标题】绩效评估的惯用方式?【英文标题】:Idiomatic way of performance evaluation? 【发布时间】:2021-12-21 18:02:53 【问题描述】:

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

程序不断运行一个主循环:

while (true) 
   doSomething()
   drawSomething()
   doSomething2()
   sendSomething()

主循环每秒运行超过 60 次。

我想查看性能细分,每个过程需要多少时间。

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

这会产生巨大的性能开销。

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

打印日志就够了?

【问题讨论】:

使用分析器? 什么语言?使用基准测试框架。 【参考方案1】:

通常:对于重复的短内容,您可以只为整个重复循环计时。 (但是微基准测试很难;除非您了解这样做的含义,否则很容易扭曲结果;对于非常短的事情,吞吐量和延迟是不同的,因此通过使一次迭代使用或不使用前一次的结果来分别测量两者。还要注意分支预测和缓存可以使某些东西在微基准测试中看起来很快,而如果在一个更大的程序中的其他工作之间一次完成一项实际上会很昂贵。 例如循环展开和查找表通常看起来不错,因为 I-cache 或 D-cache 没有其他任何压力。)

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

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

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

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

无法预热代码/数据缓存和其他内容:在定时区域内出现用于接触新内存的页面错误,或代码/数据缓存未命中,这不属于正常操作的一部分。 (注意此效果的示例:Performance: memsetwrong conclusion based on this mistake 的示例) 未能让 CPU 时间加速到最大涡轮:现代 CPU 将时钟降至空闲速度以节省电力,仅在几毫秒后才开始加速。 (或更长,具体取决于操作系统/硬件)。

相关:在现代 x86 上,RDTSC counts reference cycles, not core clock cycles,因此它受到与挂钟时间相同的 CPU 频率变化影响。

在具有乱序执行的现代 CPU 上,some things are too short to truly time meaningfully,另请参见 this。 一小块汇编语言的性能(例如,由编译器为一个函数生成)不能用单个数字来表征,即使它不分支或访问内存(所以没有机会错误预测或缓存未命中)。它从输入到输出有延迟,但如果使用独立输入重复运行不同的吞吐量会更高。例如Skylake CPU 上的 add 指令具有 4 个/时钟吞吐量,但有 1 个周期延迟。所以 dummy = foo(x) 在循环中可以比 x = foo(x); 快 4 倍。浮点指令具有比整数更高的延迟,因此通常更重要。大多数 CPU 上的内存访问也是流水线的,因此遍历数组(下一次加载的地址易于计算)通常比遍历链表快得多(下一次加载的地址在上一次加载完成之前不可用)。李>

显然,CPU 之间的性能可能不同;在大局中,A 版在 Intel 上更快,B 版在 AMD 上更快,但这种情况很容易在小范围内发生。在报告/记录基准数值时,请始终注意您测试的 CPU。

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

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

石蕊测试:

如果您在一个程序中对两个函数进行基准测试:如果颠倒测试顺序会改变结果,那么您的基准测试是不公平的。例如功能 A 可能只是看起来很慢,因为您首先要对其进行测试,并且预热不足。示例:Why is std::vector slower than an array?(不是,无论哪个循环首先运行都必须为所有页面错误和缓存未命中支付费用;第二个循环只是通过填充相同的内存进行缩放。)

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

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


对于 C / C++,另请参阅 Simple for() loop benchmark takes the same time with any loop bound,我在其中详细介绍了微基准测试并使用 volatileasm 来阻止重要工作使用 gcc/clang 进行优化。

【讨论】:

另外相关:Simple for() loop benchmark takes the same time with any loop bound 对 C++ 中的微基准测试、C++ 如何编译为 asm 以及为什么这使得微基准测试成为挑战有很大的了解。

以上是关于绩效评估的惯用方式?的主要内容,如果未能解决你的问题,请参考以下文章

有效绩效评估的 8 个提示

绩效评估与绩效反馈

如何选择2019年绩效评估体系,整合目标管理和绩效评估

第二冲刺阶段绩效评估

绩效评估

第一次冲刺团队绩效评估