我听说过使用过这个术语,但是我不确定它的含义,因此:
它的含义与锡罐上所说的完全一样-它正在衡量“小”东西的性能,例如对操作系统内核的系统调用。
危险在于人们可能会使用从微基准测试中获得的任何结果来指示优化。众所周知:
我们应该忘掉效率低下的问题,比如说大约有97%的时间:过早的优化是万恶之源” –唐纳德·努斯(Donald Knuth)
可能有许多因素会扭曲微基准测试的结果。编译器优化就是其中之一。如果要测量的操作花费的时间很少,以至于你用来测量的时间要比实际操作本身花费的时间更长,那么你的微基准测试也会出现偏差。
例如,有人可能会对for循环的开销进行微基准测试:
for
void TestForLoop() { time start = GetTime(); for(int i = 0; i < 1000000000; ++i) { } time elapsed = GetTime() - start; time elapsedPerIteration = elapsed / 1000000000; printf("Time elapsed for each iteration: %d\n", elapsedPerIteration); }
显然,编译器可以看到该循环绝对不执行任何操作,并且根本不会为该循环生成任何代码。因此,价值elapsed和elapsedPerIteration是几乎无用。
elapsed
elapsedPerIteration
即使循环执行了一些操作:
void TestForLoop() { int sum = 0; time start = GetTime(); for(int i = 0; i < 1000000000; ++i) { ++sum; } time elapsed = GetTime() - start; time elapsedPerIteration = elapsed / 1000000000; printf("Time elapsed for each iteration: %d\n", elapsedPerIteration); }
编译器可能会看到该变量sum将不会用于任何东西,并且对其进行了优化,并且也优化了for循环。可是等等!如果我们这样做:
sum
void TestForLoop() { int sum = 0; time start = GetTime(); for(int i = 0; i < 1000000000; ++i) { ++sum; } time elapsed = GetTime() - start; time elapsedPerIteration = elapsed / 1000000000; printf("Time elapsed for each iteration: %d\n", elapsedPerIteration); printf("Sum: %d\n", sum); // Added }
编译器可能很聪明,可以意识到它sum始终是一个恒定值,并且还可以对其进行优化。如今,许多人会对编译器的优化功能感到惊讶。
但是,编译器无法优化的事情呢?
void TestFileOpenPerformance() { FILE* file = NULL; time start = GetTime(); for(int i = 0; i < 1000000000; ++i) { file = fopen("testfile.dat"); fclose(file); } time elapsed = GetTime() - start; time elapsedPerIteration = elapsed / 1000000000; printf("Time elapsed for each file open: %d\n", elapsedPerIteration); }
即使这不是有用的测试!操作系统可能会看到文件被打开得非常频繁,因此它可能会将其预加载到内存中以提高性能。几乎所有操作系统都可以这样做。当你打开应用程序时,也会发生同样的事情-操作系统可能会找出最多打开的前5个应用程序,并在启动计算机时将应用程序代码预加载到内存中!
实际上,有无数变量在起作用:引用的局部性(例如数组与链表),缓存和内存带宽的影响,编译器内联,编译器实现,编译器切换,处理器核心数量,处理器级别的优化,操作系统调度程序,操作系统后台进程等。
因此,在许多情况下,微基准测试并不是完全有用的指标。它绝对不能用定义良好的测试用例(分析)代替整个程序的基准测试。首先编写可读代码,然后进行概要分析以查看需要完成的操作(如果有)。
我想强调一点,微基准本身并不是邪恶的,但人们必须谨慎使用它们(对于与计算机相关的许多其他事情,这是正确的)