惯用的性能评估方式?

15

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

该程序持续运行一个主循环:

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

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

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

我担心如果我打印每个过程的进入和退出的时间间隔,会导致巨大的性能开销。

我好奇什么是一种惯用的性能测量方法。

打印日志足够好吗?


6
使用性能分析器? - John3136
什么语言?使用基准测试框架。 - Dioxin
1个回答

45
一般而言,对于重复的短操作,您可以计时整个重复循环。(但微基准测试很困难;除非您理解这样做的影响,否则很容易扭曲结果;对于非常短的事物,吞吐量和延迟是不同的,因此可以通过使一个迭代使用上一个结果或不使用来分别测量两者。同时请注意,分支预测和缓存会使微基准测试中的某些项看起来很快,但如果在较大程序中单独执行,则实际成本可能很高。例如,循环展开和查找表通常看起来很好,因为没有任何其他数据对 I-cache 或 D-cache 施加压力。)

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

这个问题太广泛了,无法提供更具体的信息。

许多编程语言都有基准测试包,可帮助您编写单个函数的微型基准测试。例如,对于Java,JMH会确保测试函数在进行定时运行之前已经预热并完全优化,并计算完成了多少次迭代。请参见如何编写正确的Java微型基准测试?获取更多信息。请使用它们。

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

  • 未预热代码/数据缓存等会导致在定时区域内触摸新内存或代码/数据缓存未命中的页面错误,这不是正常操作的一部分。(注意此效应的示例:性能:memset; 或者基于此错误的错误结论的示例)

  • 如果没有写入过的内存(从内核获取的新内存)在读取时会将其所有页面复制到同一个系统范围的物理页面(4K或2M)上,该页面全为零。至少在Linux上是这样。因此,您可以获得高速缓存命中但TLB未命中。例如,来自new/calloc/malloc的大型分配,或在.bss的静态存储中的零初始化数组。使用非零初始化器或memset。

  • 未给CPU时间以达到最大Turbo的故障:现代CPU会降低时钟速度以节省电力,在几毫秒后才会升高。(或更长,具体取决于操作系统/硬件)。

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

  • 大多数整数和FP算术asm指令(除了除法和平方根,它们已经比其他指令慢)的性能(延迟和吞吐量)不取决于实际数据。除了次标准称为非正常浮点非常缓慢,在某些情况下(例如遗留x87但不是SSE2),还会产生NaN或Inf可能很慢。

  • 在具有乱序执行的现代CPU上,有些东西太短而不能真正有意义地计时, 另请参见这个问题一个小的汇编语言块的性能(例如由一个函数生成的编译器)不能用单个数字来描述,即使它不分支或访问内存(因此没有误判或缓存未命中的机会)。它具有从输入到输出的延迟,但如果使用独立输入重复运行,则具有不同的吞吐量更高。例如,Skylake CPU上的add指令具有4/时钟吞吐量,但具有1周期延迟。因此,在循环中dummy = foo(x)可以比x = foo(x);快4倍。浮点指令的延迟比整数高,因此通常更重要。大多数CPU上的内存访问也是流水线化的,因此循环访问数组(易于计算下一个加载的地址)通常比遍历链接列表(下一个加载的地址直到前一个加载完成后才可用)快得多。

    显然,性能可能因CPU而异;在大局观中,通常Intel上版本A较快,AMD上版本B较快,但在小规模中这很容易发生。在报告/记录基准测试数字时,请始终注意您测试的CPU。

  • 与上述和下述点相关:一般情况下无法“基准测试C中的

    与上述最后一点相关:如果函数的真实用例包括很多小输入,请不要仅针对大输入进行调整。

    例如,一个memcpy实现,虽然对于大输入非常好,但是要花费太长时间来确定如何处理小输入,可能不是很好。这是一个权衡;确保它足够好的大输入(对于“足够”的适当定义),但同时也确保小输入的开销较低。

    检验方法:

    如果在同一个程序中对两个函数进行基准测试:如果颠倒测试顺序会改变结果,那么你的基准测试就不公平。例如,函数A可能只看起来很慢,因为你首先测试它,而且热身不足。示例:为什么std::vector比数组慢?(实际上不是,无论哪个循环先运行,都必须为所有页面故障和缓存未命中付出代价;第二个只需快速填充相同的内存即可)。 增加重复循环的迭代次数应该线性增加总时间,并且不影响每次调用的计算时间。如果不是这样,那么你有非常大的测量开销或者你的代码已经被优化掉了(例如,提升出循环并且只运行一次而不是N次)。
    作为健全性检查,请改变其他测试参数。

    对于C/C++,还请参阅简单的for()循环基准测试在任何循环限制下都需要相同的时间,其中我更详细地介绍了微基准测试以及使用volatileasm来防止gcc/clang优化掉重要工作。


1
https://github.com/cyring/CoreFreq 是一个很棒的工具,可以展示现代CPU在工作负载变化时如何动态调整其工作频率。即使在操作系统没有进行任何软件频率控制的情况下(例如,当cpufreq/scaling_governor设置为performancecpu/intel_pstate/no_turbo设置时),这种调整仍然会发生。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接