绩效评估的惯用方式?

问题描述 投票:0回答:1

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

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

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

主循环每秒运行60次以上。

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

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

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

我很好奇什么是衡量表现的惯用方式。

日志记录的打印足够好吗?

benchmarking microbenchmark
1个回答
1
投票

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

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

这个问题范围太广,无法说得更具体。

许多语言都有基准测试包,可帮助您编写单个功能的微基准。使用它们。例如对于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次)。

  • 即更改测试参数以进行完整性检查。

© www.soinside.com 2019 - 2024. All rights reserved.