我正在为我的项目评估网络+渲染工作量。
程序连续运行一个主循环:
while (true) {
doSomething()
drawSomething()
doSomething2()
sendSomething()
}
主循环每秒运行60次以上。
我想查看性能细分,每个过程花费多少时间。
我担心的是,如果我为每个过程的每个入口和出口打印时间间隔,
这会产生巨大的性能开销。
我很好奇什么是衡量表现的惯用方式。
日志记录的打印足够好吗?
通常:对于重复的简短内容,您可以为整个重复循环计时。 (但是微基准测试很难;除非您了解这样做的含义,否则很容易扭曲结果。)
或者,如果您坚持对每个单独的迭代进行计时,则将结果记录在数组中,然后打印;您不想在循环中调用笨重的打印代码。
这个问题范围太广,无法说得更具体。
许多语言都有基准测试包,可帮助您编写单个功能的微基准。使用它们。例如对于Java,JMH确保在定时运行之前,JIT和所有爵士乐对被测函数进行了预热和完全优化。并以指定的间隔运行它,计算完成的迭代次数。
注意常见的微基准测试陷阱:
无法给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上进行测试。
*
运算符进行基准测试。它的某些用例将与其他用例完全不同地编译,例如循环中的tmp = foo * i;
通常可以变成tmp += foo
(强度降低),或者如果乘数是2的恒定幂,则编译器将只使用移位。源代码中的同一运算符可以根据周围的代码编译成非常不同的指令。volatile
),以便编译器必须生成结果。对输入使用随机数或其他内容,而不要使用编译时常量,这样您的编译器就无法对在实际用例中不是常量的事物进行常量传播。在C语言中,有时您可以使用内联asm或volatile
,例如东西this question is asking about。一个好的基准测试程序包,例如Google Benchmark,将为此提供功能。关于最后一点:如果函数的实际用例包括许多小输入,则不要仅针对大量输入进行调整。例如一个memcpy
实现对大量输入非常有用,但是花很长时间才能弄清楚对较小输入使用哪种策略可能不是很好。这是一个权衡;确保它对于大型输入足够好,但对于小型输入则保持较低的开销。
石蕊测试:
如果在一个程序中对两个函数进行基准测试:如果颠倒测试顺序会改变结果,则基准测试是不公平的。例如函数A可能看起来很慢,因为您要先对其进行测试,并且预热不足。例如:Why is std::vector slower than an array?(不是,首先运行的循环必须补偿所有页面错误和高速缓存未命中;第二个仅通过填充相同的内存进行缩放。)
增加重复循环的迭代次数应线性增加总时间,并且不影响计算的每次通话时间。如果不是这样,那么您将有不可忽略的测量开销或优化的代码(例如,吊起循环并仅运行一次而不是N次)。
即更改测试参数以进行完整性检查。