为什么在预热阶段浮点运算要快得多?

花月

我最初想用Java中的浮点性能优化来测试一些不同的东西,即除法5.0f和乘以之间的性能差异0.2f(没有预热的乘积似乎较慢,而乘以1.5的倍数则更快)。

在研究了结果之后,我注意到我忘记添加预热阶段,因为在进行性能优化时经常会建议这样做,所以我添加了预热阶段。而且,令我惊讶的是,在多次测试中,平均速度提高了约25倍。

我用以下代码对其进行了测试:

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: 382224
Multiply with 0.2f: 490765

结果热身阶段:

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

我无法解释的另一个有趣的变化是轮到什么运算更快(除法与乘法)。如前所述,如果不进行热身,则划分速度可能会快一点,而使用热身时,划分速度会慢两倍。

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

这种行为的原因是什么?这个预热阶段是什么?它如何影响性能?为什么在预热阶段操作如此之快?为什么要转弯以更快的速度进行操作?

克里斯·克

在热身的Java将通过解释器运行字节码之前,请考虑如何编写可以在Java中执行Java字节码的程序。预热后,热点将为您正在运行的cpu生成本机汇编程序。利用该cpus功能集。两者之间存在明显的性能差异,解释器将为单个字节代码运行许多cpu指令,因为热点会像gcc编译C代码时那样生成本地汇编代码。那就是除法和乘法时间之间的差最终将取决于运行一个CPU的时间,而这仅仅是一条CPU指令。

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

简而言之,必须在预热前放弃结果。

作者Brian Goetz写了一篇很好的文章在这里关于这个问题的。

========

附录:“ JVM预热”含义的概述

JVM“热身”是一个宽松的词,不再严格地讲是JVM的单个阶段或阶段。人们倾向于使用它来指代将JVM字节码编译为本地字节码后JVM性能稳定的地方。实际上,当人们开始摸索并深入研究JVM内部时,很难不对Hotspot对我们所做的贡献印象深刻。我在这里的目的只是让您更好地了解Hotspot以表演的名义可以做的事情,有关更多详细信息,我建议阅读Brian Goetz,Doug Lea,John Rose,Cliff Click和Gil Tene(以及许多其他文章)中的文章。

如前所述,JVM首先通过其解释器运行Java。虽然严格说来不是100%正确,但可以将解释器视为大型switch语句和循环遍历每个JVM字节代码(命令)的循环。switch语句中的每种情况都是一个JVM字节码,例如将两个值加在一起,调用方法,调用构造函数等等。迭代以及在命令周围跳转的开销非常大。因此,单个命令的执行通常将使用超过10倍的汇编命令,这意味着>慢10倍以上,因为硬件必须执行更多的命令,并且此解释器代码会污染高速缓存,因此理想情况下,我们宁愿专注于实际程序。回想一下Java的早期,那时Java赢得了非常慢的声誉。

后来,在将JIT编译器添加到Java时,这些编译器将在调用方法之前将Java方法编译为本机CPU指令。这消除了解释程序的所有开销,并允许在硬件中执行代码。虽然在硬件中执行速度要快得多,但是这种额外的编译在Java启动时造成了停顿。这部分是“热身阶段”的术语盛行的地方。

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

因此,在这种情况下,总而言之,我们指的是在微基准测试中预热JVM,将目标代码运行10k次以上并丢弃结果,以使JVM有机会收集统计信息并优化JVM的“热区”。代码。10k是一个神奇的数字,因为Server Hotspot实现在开始考虑优化之前会等待许多方法调用或循环迭代。我还建议在核心测试运行之间进行方法调用,因为尽管热点可以“替换堆栈”(OSR),但在实际应用程序中并不常见,并且其行为与换出整个方法的实现并不完全相同。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

为什么在预热阶段浮点运算要快得多?

来自分类Dev

为什么EnumerateFiles比计算大小要快得多

来自分类Dev

为什么memcmp比for循环检查要快得多?

来自分类Dev

为什么memcmp比for循环检查要快得多?

来自分类Dev

为什么wget比通过Chrome下载要快得多?

来自分类Dev

为什么<比!=快得多?

来自分类Dev

为什么SqlQuery比在视图上使用LINQ表达式要快得多?

来自分类Dev

为什么列表理解比附加列表要快得多?

来自分类Dev

为什么通过引用返回向量比通过移动返回向量要快得多?

来自分类Dev

为什么字典的项方法比直接迭代要快得多?

来自分类Dev

为什么通过gzip传递'dd'比直接复制要快得多?

来自分类Dev

为什么Array.map(比)尾递归映射要快得多?

来自分类Dev

为什么列表理解比数组乘以numpy要快得多?

来自分类Dev

性能:为什么相同算法的第一个实现速度要快得多

来自分类Dev

为什么StringBuilder比String快得多

来自分类常见问题

为什么字典比列表快得多?

来自分类Dev

为什么PreparedStatement比Statement快得多?

来自分类Dev

为什么熊猫.isin比“ in”快得多?

来自分类Dev

为什么第一次运行HttpClient的速度很慢,但是却要快得多?

来自分类Dev

Android:为什么本机代码比Java代码快得多

来自分类Dev

Python清单:为什么.sort()比sorted()快得多?

来自分类Dev

为什么批处理模式比parfor快得多?

来自分类Dev

为什么ifstream :: read比使用迭代器快得多?

来自分类Dev

为什么strcmp比我的函数快得多?

来自分类Dev

为什么数组的直接索引比迭代快得多?

来自分类Dev

为什么Python中的这段代码比C ++快得多?

来自分类Dev

为什么SSH感觉比HTTP快得多?

来自分类Dev

为什么strcmp比我的函数快得多?

来自分类Dev

为什么子图比图快得多?

Related 相关文章

  1. 1

    为什么在预热阶段浮点运算要快得多?

  2. 2

    为什么EnumerateFiles比计算大小要快得多

  3. 3

    为什么memcmp比for循环检查要快得多?

  4. 4

    为什么memcmp比for循环检查要快得多?

  5. 5

    为什么wget比通过Chrome下载要快得多?

  6. 6

    为什么<比!=快得多?

  7. 7

    为什么SqlQuery比在视图上使用LINQ表达式要快得多?

  8. 8

    为什么列表理解比附加列表要快得多?

  9. 9

    为什么通过引用返回向量比通过移动返回向量要快得多?

  10. 10

    为什么字典的项方法比直接迭代要快得多?

  11. 11

    为什么通过gzip传递'dd'比直接复制要快得多?

  12. 12

    为什么Array.map(比)尾递归映射要快得多?

  13. 13

    为什么列表理解比数组乘以numpy要快得多?

  14. 14

    性能:为什么相同算法的第一个实现速度要快得多

  15. 15

    为什么StringBuilder比String快得多

  16. 16

    为什么字典比列表快得多?

  17. 17

    为什么PreparedStatement比Statement快得多?

  18. 18

    为什么熊猫.isin比“ in”快得多?

  19. 19

    为什么第一次运行HttpClient的速度很慢,但是却要快得多?

  20. 20

    Android:为什么本机代码比Java代码快得多

  21. 21

    Python清单:为什么.sort()比sorted()快得多?

  22. 22

    为什么批处理模式比parfor快得多?

  23. 23

    为什么ifstream :: read比使用迭代器快得多?

  24. 24

    为什么strcmp比我的函数快得多?

  25. 25

    为什么数组的直接索引比迭代快得多?

  26. 26

    为什么Python中的这段代码比C ++快得多?

  27. 27

    为什么SSH感觉比HTTP快得多?

  28. 28

    为什么strcmp比我的函数快得多?

  29. 29

    为什么子图比图快得多?

热门标签

归档