2014-08-30 17 views
3

我最初想要测试一些与Java中浮点性能优化不同的东西,即除法除以5.0f和乘以0.2f(乘法似乎在没有预热的情况下比较慢,但以大约1.5倍分别)。为什么在预热阶段浮点运算更快?

研究结果后,我注意到我忘了添加一个热身阶段,正如经常进行性能优化时所建议的那样,所以我添加了它。而且,令我非常吃惊的是,在多次测试中,结果平均快了25倍。与预热阶段

Divide by 5.0f: 382224 
Multiply with 0.2f: 490765 

结果

我用下面的代码测试了它:

public static void main(String args[]) 
{ 
    float[] test = new float[10000]; 
    float[] test_copy; 

    //warmup 
    for (int i = 0; i < 1000; i++) 
    { 
     fillRandom(test); 

     test_copy = test.clone(); 

     divideByTwo(test); 
     multiplyWithOneHalf(test_copy); 
    } 

    long divisionTime = 0L; 
    long multiplicationTime = 0L; 

    for (int i = 0; i < 1000; i++) 
    { 
     fillRandom(test); 

     test_copy = test.clone(); 

     divisionTime += divideByTwo(test); 
     multiplicationTime += multiplyWithOneHalf(test_copy); 
    } 

    System.out.println("Divide by 5.0f: " + divisionTime); 
    System.out.println("Multiply with 0.2f: " + multiplicationTime); 
} 

public static long divideByTwo(float[] data) 
{ 
    long before = System.nanoTime(); 

    for (float f : data) 
    { 
     f /= 5.0f; 
    } 

    return System.nanoTime() - before; 
} 

public static long multiplyWithOneHalf(float[] data) 
{ 
    long before = System.nanoTime(); 

    for (float f : data) 
    { 
     f *= 0.2f; 
    } 

    return System.nanoTime() - before; 
} 

public static void fillRandom(float[] data) 
{ 
    Random random = new Random(); 

    for (float f : data) 
    { 
     f = random.nextInt() * random.nextFloat(); 
    } 
} 

结果而不预热阶段

Divide by 5.0f: 22081 Multiply with 0.2f: 10885 

我无法解释的另一个有趣的变化是什么操作更快的转向(分割与乘法)。如前所述,没有热身赛,分区似乎有点快,而在热身之后,似乎要慢两倍。

我尝试添加一个初始化块,将值设置为随机值,但它并不影响结果,也没有添加多个预热阶段。方法操作的数字是相同的,所以不能成为原因。

这种行为的原因是什么?这是什么热身阶段,它是如何影响性能的,为什么在预热阶段操作速度更快,为什么操作速度更快呢?

回答

12

热身之前Java将通过解释器运行字节代码,请考虑如何编写可以在java中执行java字节代码的程序。热身后,热点将为您正在运行的CPU生成本地汇编器;利用该cpus功能集。这两者之间有着显着的性能差异,解释器将为单字节代码运行许多cpu指令,而热点生成本地汇编代码就像编译C代码时gcc所做的那样。这是划分时间和乘法时间之间的差异最终会落到正在运行的CPU上,而且它将只是一个cpu指令。

难题的第二部分是热点还记录统计数据,这些统计数据测量代码的运行时行为,当它决定优化代码时,它将使用这些统计数据执行优化,这些优化在编译时不一定可行。例如,它可以降低空检查,分支错误预测和多态方法调用的成本。

总之,必须放弃预热的结果。

Brian Goetz在这个问题上写了一篇很好的文章here

========

附:什么“JVM热身”是指概述

JVM“热身”是一个松散的短语,并且不再严格地说单JVM的阶段或阶段。人们倾向于用它来指JVM字节码编译为本地字节码后JVM性能稳定的想法。事实上,当一个人开始在表面下划伤并深入研究JVM内部时,很难让Hotspot为我们做的事情留下深刻的印象。我的目标只是让你更好地感受Hotspot能够以表演的名义所做的更多细节,我推荐阅读Brian Goetz,Doug Lea,John Rose,Cliff Click和Gil Tene等人的文章。

如前所述,JVM通过运行Java的解释器启动。尽管严格来说不是100%正确的,但可以将解释器看作是一个大的开关语句和一个遍历每个JVM字节码(命令)的循环。 switch语句中的每种情况都是JVM字节码,如将两个值一起添加,调用方法,调用构造函数等等。迭代的开销和跳过命令非常大。因此执行单个命令通常会使用10倍以上的汇编命令,这意味着速度要慢10倍以上,因为硬件必须执行如此多的命令和缓存会受到该解释器代码的污染,理想情况下,我们更愿意将注意力集中在我们的实际程序上。回想一下Java的早期时代,Java赢得了非常缓慢的声誉;这是因为它最初只是一种完全解释的语言。

后来JIT编译器被添加到Java中,这些编译器会在调用方法之前将Java方法编译为本地CPU指令。这消除了解释器的所有开销,并允许以硬件执行代码的执行。虽然硬件内部执行速度更快,但这种额外的编译在启动Java时创建了一个停顿。这部分是'暖身阶段'的术语所占据的地方。

向JVM引入Hotspot是一个改变游戏规则的游戏。现在JVM的启动速度会更快,因为它将开始运行带有解释器的Java程序,并且单个Java方法将在后台线程中编译并在执行期间实时交换出去。本地代码的生成也可以通过不同级别的优化来完成,有时使用严格不正确的非常积极的优化,然后在必要时进行非优化和重新优化以确保正确的行为。例如,类层次结构意味着花费很大的代价来确定将调用哪个方法,因为热点必须搜索层次结构并找到目标方法。在这里热点可以变得非常聪明,如果它注意到只有一个类已经被加载,那么它可以假设将总是如此,并优化和内联这样的方法。如果另一个类被加载,现在告诉Hotspot实际上两个方法之间会做出决定,那么它将删除它以前的假设并在飞行中重新编译。在不同情况下可以做出的优化的完整列表非常令人印象深刻,并且不断变化。热点能够记录有关运行环境的信息和统计信息,以及当前正在经历的工作负载,使得优化过程非常灵活和动态。事实上,在单个Java进程的整个生命周期内,很可能随着工作负载的变化,该程序的代码将重新生成很多次。可以说Hotspot比传统的静态编译具有更大的优势,这也是很大程度上为什么很多Java代码可以被认为与编写C代码一样快的原因。这也使得理解微基准变得更加困难。实际上它让Oracle的维护人员对JVM代码本身的理解,处理和诊断问题变得更加困难。花一分钟时间向这些人提供一个品脱点,Hotspot和JVM作为一个整体是一个奇妙的工程胜利,在人们说它无法完成的时候,它就脱颖而出。值得记住的是,由于十年左右它是一个相当复杂的野兽;)

因此,鉴于上下文,总结我们指的是在microbenchmarks中预热一个JVM,使其运行超过10k次的目标代码并抛出结果使JVM有机会收集统计数据并优化代码的“热区”。 10k是一个幻数,因为Server Hotspot实现在开始考虑优化之前等待许多方法调用或循环迭代。我还会建议在核心测试运行之间进行方法调用,因为虽然热点可以“在堆栈替换”(OSR)上执行,但它在实际应用中并不常见,并且与交换方法的整个实现的行为不完全相同。

+0

你能否详细说明一下暖机阶段如何影响它,以及它是如何工作的?我还没有完全理解它,在其他地方似乎没有太多的解释。 – 1337 2014-08-31 09:41:34

+0

@ 1337这不是一个快速回答的问题,但我在问题末尾添加了一个部分以提供更多详细信息。没有一个简单的答案,因为“热身”实际上是一个用来覆盖JVM许多方面的松散术语,但我会放弃它。 – 2014-08-31 18:27:44

+0

令人惊叹。非常感谢。我会为你额外提供50点声望赏金,但我认为我必须再等一天才能投入奖金:) – 1337 2014-08-31 18:56:43

4

你没有测量任何有用的“没有预热阶段”;您正在测量解释代码的速度,以及生成堆栈替换需要多长时间。也许分裂会导致更早的编制。

有许多指导方针和各种软件包可用于构建不受此类问题影响的微基准标记。如果你打算继续做这种事情,我建议你阅读指南并使用现成的软件包。

+0

尽管这确实回答了我的问题的第一部分,但它仍未回答第二部分:为什么轮到哪个操作更快? – 1337 2014-08-30 14:24:47

+2

它非常回答你的问题的一部分。分割码可能比乘法码快得多。 – tmyklebu 2014-08-30 14:25:26

+2

@ 1337克里斯回答说,因为它是由于热点jvm。 – 2014-08-30 14:25:49

相关问题