什么是microbenchmarking?

时间:2010-05-16 05:41:19

标签: java performance optimization benchmarking jit

我听说过这个术语,但我不完全确定它是什么意思,所以:

  • 它意味着什么,它的意思是什么?
  • IS和IS不是微基准测试的一些例子是什么?
  • 微基准测试的危险是什么?如何避免?
    • (或者这是好事吗?)

6 个答案:

答案 0 :(得分:64)

这意味着它在锡罐上的确切含义 - 它测量的是“小”的性能,就像系统调用操作系统的内核一样。

危险在于人们可能会使用从微基准测试中获得的任何结果来指示优化。众所周知:

  

我们应该忘记效率很低,大约97%的时间都是如此:过早优化是其中的根源   所有的邪恶“ - 唐纳德克努特

可能有许多因素扭曲了微基准测试的结果。编译器优化就是其中之一。如果被测量的操作花费的时间很少,那么无论您使用什么测量它都需要比实际操作本身更长的时间,您的微基准测试也会有所偏差。

例如,有人可能会采用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个应用程序,并在启动计算机时将应用程序代码预加载到内存中!

事实上,有无数的变量发挥作用:引用的位置(例如数组与链表),缓存和内存带宽的影响,编译器内联,编译器实现,编译器开关,处理器内核数量,优化处理器级别,操作系统调度程序,操作系统后台进程等

因此,在很多情况下,微基准测试并不是一个有用的指标。它肯定不会用明确定义的测试用例(分析)取代整个程序基准。首先编写可读代码,然后编写配置文件以查看需要执行的操作(如果有)。

我想强调微基准不是邪恶的本身,但是必须小心使用它们(对于许多与计算机相关的其他事情都是如此)

答案 1 :(得分:6)

没有微基准测试的定义,但是当我使用它时,我的意思是一个小的人工基准测试,用于测试某些特定硬件 1 或语言功能的性能。相比之下,更好的基准是一个真正的程序,旨在执行一项真正的任务。 (在两个案例之间划清界限是毫无意义的,IMO,我不会尝试。)

微观基准测试的危险在于,编写基准测试很容易产生完全误导性的结果。 Java微基准测试中的一些常见陷阱是:

  • 编写编译器可以推导出的代码并不是有用的工作,因此完全优化,
  • 没有考虑Java内存管理的“块状”性质,
  • 没有考虑JVM启动效果;例如加载和JIT编译类的时间,以及(相反)一旦方法被JIT编译后发生的执行加速。

然而,即使您已经解决了上述问题,但基准测试还存在一个无法解决的系统性问题。基准的代码和行为通常与您真正关心的内容没有多大关系;即你的应用程序将如何执行。你有太多的“隐藏变量”可以从基准测试到典型的程序,更不用说你的程序了。

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

@BalusC在Hotspot FAQ页面中提供了有关此主题材料的出色链接。以下是Brian Goetz的IBM白皮书链接。


1 - 专家甚至不会尝试在Java中进行硬件基准测试。在字节码和硬件之间发生了太多“复杂的事情”,从原始结果中得出有关硬件的有效/有用的结论。你最好使用更接近硬件的语言;例如C甚至汇编代码。

答案 2 :(得分:4)

  
      
  • 它意味着什么,它的意思是什么?
  •   

我认为微基准测试只是意味着测量微小的东西。 Tiny可能依赖于上下文,但通常在单个系统调用或类似的事件上。基准测试指的是上述所有内容。

  
      
  • IS和IS不是微基准测试的一些例子是什么?
  •   

This article列出测量getpid()系统调用的时间使用memcpy()测量复制内存的时间作为微基准测试的示例。

任何算法实现等的测量都不算作微基准测试。特别是列出执行时间缩短的任务的结果报告可能很少被视为微基准测试。

  
      
  • 微基准测试有哪些危险?如何避免?
  •   

明显的危险是它会诱使开发人员优化程序的错误部分。另一个危险是,很难准确地测量小的东西。避免它的最简单方法可能只是为了更好地了解程序中花费的时间。

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

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

与其他人一样,这本身并不是一件坏事,很多网页似乎都在暗示。它有它的位置。我使用程序重写和运行时方面编织等。我们通常发布我们添加的指令的微基准,不是为了指导任何优化,而是确保我们的额外代码对重写程序的执行几乎没有影响。

然而,这是一门艺术,尤其是在具有JIT,预热时间等的VM环境中。描述了一个很好描述的Java方法here

答案 3 :(得分:2)

书中的

(Java Performance The Definitive Guide) 它有这个定义和例子,没有微观基准

  1. 微基准

    微基准测试是一种用于测量非常小的单位的测试 性能:调用同步方法的时间与 非同步方法;创建线程与asing的开销 一个线程池;是时候执行一个算术算法而不是 替代实施;等等。

    Microbenchmarks似乎是一个好主意,但它们非常好 很难正确写。 consedider以下代码,其中 试图写一个测试permorfance od的mocrobenchmark 计算第50个Fibonaccio的方法的不同实现 号:

  2. 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;
    }
    

    微型车标记必须使用他们的结果。

    此代码的最大问题是它实际上从未改变任何程序状态。因为Fibonacci计算的结果从未使用过,编译器可以自由地丢弃该计算,一个智能编译器(包括当前的java 7和8编译器)

    将最终执行此代码:

    long then = System.currentTimeMillis();
    long now = System.currentTimeMillis();
    System.out.println("Elapsed time: " + (now - then));
    

    结果,无论fibonaci方法的实现方式,或者循环应该执行的次数,经过的时间只有几毫秒。

    围绕这个特定问题有一种解决方法:确保每个结果都被读取,也不仅仅是我们的结果。实际上,将l的定义从局部变量更改为实例变量(使用volatile关键字声明)将允许测量方法的性能。

答案 4 :(得分:1)

以下是Brian Goetz的一些好文章,解释了为什么(微观)基准测试在Java中特别难:

答案 5 :(得分:-1)

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

一般来说,微基准测试(如计算机科学所说)试图测量一些非常精细的任务的性能,这在任何实际性能问题的背景下都难以做好并且通常毫无意义。