什么是微基准测试?

90

我听说过这个词,但不太确定它的意思:

  • 它是什么意思,又不是什么意思?
  • 有哪些例子是微基准测试,哪些不是?
  • 微基准测试的危险在哪里,如何避免?
    • (或者它是好事吗?)

6
现在是我的就寝时间,这里只有一个带有链接的愚蠢评论,以帮助您开始阅读材料:http://java.sun.com/docs/hotspot/HotSpotFAQ.html(请查看目录底部的“基准测试”章节)。我会为您提供翻译,请您核对是否准确无误。 - BalusC
11
只有基准测试的百万分之一有用。 - Stephen C
1
http://code.google.com/p/caliper/wiki/JavaMicrobenchmarks - polygenelubricants
6个回答

109
它的意思与罐头上标注的一样 - 它测量了某些东西(例如系统调用到操作系统内核)的性能,但规模很小。
危险在于人们可能会使用从微基准测试中获得的任何结果来决定优化。正如我们所知道的:
“我们应该忘记小的效率问题,即 97% 的时间:过早地进行优化是万恶之源”--Donald Knuth
微基准测试的结果可能会受到许多因素影响,编译器优化就是其中之一。如果被测量的操作需要的时间太少,以至于用于测量它的工具所花费的时间比实际操作本身还长,那么你的微基准测试结果也会出现偏差。
例如,有人可能会对 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);
}

显然编译器可以看出循环根本不执行任何操作,所以完全不需要为循环生成任何代码。因此elapsedelapsedPerIteration的值几乎没有用处。

即使循环执行了某些操作:

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个应用程序,并在计算机启动时将应用程序代码预加载到内存中!
实际上,有无数的变量需要考虑:引用局部性(例如数组与链表),缓存和内存带宽的影响,编译器内联,编译器实现,编译器开关,处理器核心数量,处理器级别的优化,操作系统调度程序,操作系统后台进程等。
因此,在许多情况下,微基准测试并不是一个很有用的指标。它绝对不能替代带有明确定义的测试用例(分析)的整个程序基准测试。首先编写易读的代码,然后进行分析以查看是否需要进行改进。
我想强调的是,微基准测试本身并不是邪恶的,但必须小心使用(这对于与计算机相关的许多其他事情也是如此)。

10
好的评论,不过Knuth的意思是尽早考虑优化不应影响设计(而不是“指导优化”)。根据早期基准测试结果来调整设计通常会导致设计不灵活。http://en.wikipedia.org/wiki/Program_optimization - Eric J.
1
正确,但我想补充的是,优化程序的方式可能会影响其设计。我的观点是微基准测试很少提供有用的信息。 - In silico
这些程序是否真的应该打印“overhead”?因为打印出来的不是开销,而是每次迭代的总时间。 - Thomas Padron-McCarthy
我将它改成了“消耗时间”,我认为这可能更准确地描述我们要测量的内容。但是对于微基准测试而言,你所测量的可能与实际代码本身无关! - In silico
2
实际上,Knuth 指的是在对软件执行过程了解甚少的情况下进行的性能优化。 - William Louth
看到文件被频繁打开,因此可以预加载它。对于任何真实的操作系统,在打开/关闭文件后,文件的元数据仍然会保留在VFS缓存中。它不必检测任何特殊模式或其他内容,只需像文件系统元数据(例如Linux VFS缓存)和数据(页面缓存)的普通伪LRU缓存一样工作即可。这并没有什么惊人或特别之处。众所周知,例如“time find /usr/lib > /dev/null”第二次运行速度要快得多。 - Peter Cordes

11

注:鉴于提问者使用的标签,本回答以Java为中心。

没有对微基准测试进行明确定义,但是当我使用它时,我的意思是一种小规模人工基准测试,旨在测试某些特定硬件1或语言特性的性能。相比之下,更好的(Java)基准测试是一个设计用于执行实际任务的真实程序。(在这两种情况之间划清界限毫无意义,个人认为不必尝试。)

微基准测试的危险在于很容易编写出完全误导人的基准测试结果。Java微基准测试中常见的一些陷阱包括:

  • 编写编译器可以推断出根本没有有用工作并因此完全优化掉的代码,
  • 没有考虑Java内存管理的“不均匀”特性(即垃圾收集),以及
  • 没有考虑JVM启动效应;例如加载和JIT编译类所需的时间,以及(相反)方法被JIT编译后发生的执行加速。

然而,即使您已经解决了上述问题,微基准测试仍存在系统性问题,这是不可能解决的。微基准测试的代码和行为通常与您真正关心的内容关系甚微;也就是说,您的应用程序将如何执行。由于存在太多“隐藏变量”,您无法从微基准测试中推广到典型的Java程序,更不用说到您的程序了。

出于这些原因,我们经常建议人们不要浪费时间进行微基准测试。相反,最好编写简单自然的代码,并使用分析器识别需要手动优化的区域。通常会发现,典型实际应用程序中最重要的性能问题是由于数据结构和算法的设计不良(包括网络、数据库和线程相关瓶颈)而不是微基准测试试图测试的这种细粒度的问题。

@BalusC提供了一个关于这个话题的材料的绝佳链接,在Hotspot FAQ页面上。这里还有IBM的一篇白皮书,作者是Brian Goetz

另请参见:如何编写正确的Java微基准测试?


1- 专家甚至不会尝试在Java中进行硬件基准测试。在字节码和硬件之间发生太多“复杂的事情”,以从原始结果中得出有效/有用的结论。您最好使用更接近硬件的语言,例如C甚至汇编代码。
2- 我断言这一点,但没有提供任何支持证据。当然也有例外。


6
我认为微基准测试只是指测量小的东西。小可能是上下文相关的,但通常在单个系统调用或类似的级别上。基准测试涵盖了所有内容。
一些微基准测试的例子包括:测量 getpid() 系统调用的时间和使用 memcpy() 复制内存的时间。任何算法实现等的测量都不算作微基准测试。特别是那些列出执行时间逐渐减少的任务的结果报告很少被视为微基准测试。
微基准测试的危险在于过度关注细节而忽略了整体性能。为避免这种情况,应该将微基准测试与其他性能测试结合起来,并且要确保测试环境与生产环境相同。
明显的危险是它诱使开发者优化程序中错误的部分。另一个危险是准确测量小型内容非常困难。最简单的避免方法可能就是了解程序中耗时最长的部分。

人们通常说“不要进行微基准测试”,但他们可能的意思是“不要根据微基准测试做出优化决策”。

  • (或者这是件好事吗?)

本质上,这并不是一件坏事,正如其他人在这里和许多网页上所建议的那样。它有其用处。我从事程序重写、运行时方面编织等工作。我们通常会发布我们添加指令的微基准测试,不是为了引导任何优化,而是确保我们额外的代码对重写程序的执行影响很小。

然而,这是一门艺术,特别是在具有JIT、预热时间等的VM上下文中。Java的一个良好描述的方法在这里(已存档)中描述。


回复:热身等:请参见惯用的性能评估方式?,了解在现代CPU和操作系统上未能做到这一点的一些陷阱。 - Peter Cordes

5

书籍《Java性能优化指南》中关于微基准测试的定义和示例如下:

  1. 微基准测试

    微基准测试是一种旨在测量非常小的单元性能的测试:调用同步方法与非同步方法的时间;创建线程与使用线程池的开销;执行一种算法与另一种实现的时间等。

    微基准测试可能看起来是一个好主意,但它们很难正确编写。考虑以下代码,这是一个尝试编写微基准测试以测试计算第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));
作为结果,无论是斐波那契方法的实现还是循环执行的次数,经过的时间只有几毫秒。
有一种解决这个问题的方法:确保每个结果都被读取,而不仅仅是写入。在实践中,将l的定义从局部变量更改为实例变量(使用volatile关键字声明)将允许测量该方法的性能。

你几乎总是需要查看优化编译器的汇编语言输出,以确保你的微基准测试确实测量了你想要的内容。很容易出现意外的优化结果。我完全同意它们很难正确编写。因此,许多关于性能的问题在SO上得到的评论都像“为什么不自己测量一下呢?”,好像对于某些人来说测量他们甚至没有完全理解的东西是很容易的。 - Peter Cordes

2

-1

微基准测试是我认为不值得的基准测试。有效的基准测试是我认为值得花时间去做的基准测试。

一般来说,微基准测试(如silico所说)试图测量某些非常细粒度的任务的性能,这既难以做到又通常在实际性能问题的情况下毫无意义。


那么你的定义是微基准测试根本没有任何好处,对吗?这也是我的印象,但我不想排除任何可能性,在某些我需要关心的情况下它可能实际上是“有用”的。 - polygenelubricants
2
微基准测试在性能工程师的工具集中有其位置。不幸的是,大多数工程师不是性能工程师,这意味着您会得到有缺陷的测试和结果。一个好的微基准测试可以揭示各种操作的单元成本,当完整基准测试不能代表您的应用程序软件和系统执行模型时,它可以更好地服务于分析。 - William Louth

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