我听说过这个词,但不太确定它的意思:
- 它是什么意思,又不是什么意思?
- 有哪些例子是微基准测试,哪些不是?
- 微基准测试的危险在哪里,如何避免?
- (或者它是好事吗?)
我听说过这个词,但不太确定它的意思:
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
的值几乎没有用处。
即使循环执行了某些操作:
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循环。但是等等!如果我们这样做呢: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个应用程序,并在计算机启动时将应用程序代码预加载到内存中!注:鉴于提问者使用的标签,本回答以Java为中心。
没有对微基准测试进行明确定义,但是当我使用它时,我的意思是一种小规模人工基准测试,旨在测试某些特定硬件1或语言特性的性能。相比之下,更好的(Java)基准测试是一个设计用于执行实际任务的真实程序。(在这两种情况之间划清界限毫无意义,个人认为不必尝试。)
微基准测试的危险在于很容易编写出完全误导人的基准测试结果。Java微基准测试中常见的一些陷阱包括:
然而,即使您已经解决了上述问题,微基准测试仍存在系统性问题,这是不可能解决的。微基准测试的代码和行为通常与您真正关心的内容关系甚微;也就是说,您的应用程序将如何执行。由于存在太多“隐藏变量”,您无法从微基准测试中推广到典型的Java程序,更不用说到您的程序了。
出于这些原因,我们经常建议人们不要浪费时间进行微基准测试。相反,最好编写简单自然的代码,并使用分析器识别需要手动优化的区域。通常会发现,典型实际应用程序中最重要的性能问题是由于数据结构和算法的设计不良(包括网络、数据库和线程相关瓶颈)而不是微基准测试试图测试的这种细粒度的问题。
@BalusC提供了一个关于这个话题的材料的绝佳链接,在Hotspot FAQ页面上。这里还有IBM的一篇白皮书,作者是Brian Goetz。
另请参见:如何编写正确的Java微基准测试?
1- 专家甚至不会尝试在Java中进行硬件基准测试。在字节码和硬件之间发生太多“复杂的事情”,以从原始结果中得出有效/有用的结论。您最好使用更接近硬件的语言,例如C甚至汇编代码。
2- 我断言这一点,但没有提供任何支持证据。当然也有例外。
人们通常说“不要进行微基准测试”,但他们可能的意思是“不要根据微基准测试做出优化决策”。
- (或者这是件好事吗?)
本质上,这并不是一件坏事,正如其他人在这里和许多网页上所建议的那样。它有其用处。我从事程序重写、运行时方面编织等工作。我们通常会发布我们添加指令的微基准测试,不是为了引导任何优化,而是确保我们额外的代码对重写程序的执行影响很小。
然而,这是一门艺术,特别是在具有JIT、预热时间等的VM上下文中。Java的一个良好描述的方法在这里(已存档)中描述。
书籍《Java性能优化指南》中关于微基准测试的定义和示例如下:
微基准测试
微基准测试是一种旨在测量非常小的单元性能的测试:调用同步方法与非同步方法的时间;创建线程与使用线程池的开销;执行一种算法与另一种实现的时间等。
微基准测试可能看起来是一个好主意,但它们很难正确编写。考虑以下代码,这是一个尝试编写微基准测试以测试计算第50个斐波那契数的不同实现性能的示例:
public void doTest(){
double l;
long then = System.currentTimeMillis();
for(int i = 0; i < nLoops; i++){
l = fibImpl1(50);
}
long now = system.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then))
}
...
private double fibImpl1(int n){
if(n < 0) throw new IllegalArgumentException("Must be > 0");
if(n == 0) return 0d;
if(n == 1) return 1d;
double d = fibImpl1(n - 2) + fibImpl(n - 1);
if(Double.isInfinited(d)) throw new ArithmeticException("Overflow");
return d;
}
微基准测试必须使用它们的结果。
这段代码最大的问题在于它实际上从未改变任何程序状态。因为斐波那契计算的结果从未被使用,编译器可以自由地丢弃该计算。聪明的编译器(包括当前的Java 7和8编译器)将最终执行以下代码:
long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
作为结果,无论是斐波那契方法的实现还是循环执行的次数,经过的时间只有几毫秒。微基准测试是我认为不值得的基准测试。有效的基准测试是我认为值得花时间去做的基准测试。
一般来说,微基准测试(如silico所说)试图测量某些非常细粒度的任务的性能,这既难以做到又通常在实际性能问题的情况下毫无意义。