GTD
V2EX  ›  Java

jdk 8 怎么和 jdk 15.1 差距这么大?

  •  1
     
  •   GTD · Nov 19, 2020 · 7470 views
    This topic created in 2026 days ago, the information mentioned may be changed or developed.

    楼主写了两个排序方法,一个是选择排序,一个是插入排序,代码肯定是没有任何问题的

    在 macOS 下的 jdk 15.1,跑出来是这样的:

    在虚拟机 ubuntu 的 openjdk 8 中,跑出来是这样的:

    为什么会这样呢?同样一份代码,在 jdk 15.1 下,插入排序的 random array 怎么比选择排序慢了这么多?

    Supplement 1  ·  Nov 19, 2020
    Supplement 2  ·  Nov 20, 2020
    新发现,如果添加上 -XX:+UseParallelOldGC 这个参数,这个问题就会得到极大的缓解。
    24 replies    2020-11-24 01:51:59 +08:00
    nguoidiqua
        1
    nguoidiqua  
       Nov 19, 2020
    Ubuntu 上用 openJDK15 跑呢?
    GTD
        2
    GTD  
    OP
       Nov 19, 2020
    另附一个 jdk 15.1 在 macOS 终端跑的截图,因为试了很多次,应该不存在随机性
    ![]( https://tva1.sinaimg.cn/large/0081Kckwgy1gkupk5dpa9j30xs0qsnej.jpg)
    jedrek
        3
    jedrek  
       Nov 19, 2020
    楼主什么虚拟机软件?
    GTD
        4
    GTD  
    OP
       Nov 19, 2020
    @nguoidiqua #1 试了一下,用了 openjdk 11,神奇的事情出现了

    竟然跟 macOS 的 15.1 一样,插入排序慢很多

    ![]( https://tva1.sinaimg.cn/large/0081Kckwgy1gkupvmzet7j315e0pyn3f.jpg)
    GTD
        5
    GTD  
    OP
       Nov 19, 2020
    @jedrek #3 vmware fusion 12 pro
    GTD
        6
    GTD  
    OP
       Nov 19, 2020
    这是我插入排序的源代码:

    public static <E extends Comparable<E>> void sort(E[] arr){

    for(int i = 0; i < arr.length; i ++){


    E t = arr[i];
    int j;
    for(j = i; j - 1 >= 0 && t.compareTo(arr[j - 1]) < 0; j --){
    arr[j] = arr[j - 1];
    }
    arr[j] = t;
    }
    }
    40EaE5uJO3Xt1VVa
        7
    40EaE5uJO3Xt1VVa  
       Nov 19, 2020   ❤️ 1
    虽然不知道为什么,我感觉你需要 www.injdk.cn
    oldshensheep
        8
    oldshensheep  
       Nov 19, 2020
    看代码运行结果,可以看出来代码的随机数种子应该是固定的。所以可以排除随机数种子的问题。
    那就可能是 jdk 版本不同,生成的随机数不同?但是这感觉不太可能,根据结果看生成的随机数应该是一样的。
    那就是虚拟机的问题?
    还是什么……
    可以把全部代码贴上来,测试测试。推荐在这个网站粘贴 https://paste.ubuntu.com/
    GTD
        9
    GTD  
    OP
       Nov 19, 2020
    @oldshensheep #8 多谢

    应该不是随机数的问题,也不是 ubuntu 的问题,因为在 ubuntu 不同的 jdk,最后时间也不一样

    代码: https://paste.ubuntu.com/p/NFcDMsvNdH/
    jasonkayzk
        10
    jasonkayzk  
       Nov 19, 2020
    难道是 GC 的问题?
    说实话,只有 JDK11 刚出的时候,研究了一下 ZGC ;后面,因为 JDK 11 目前不支持 Android,后面又用回 8 了;
    之后再也没研究过 JDK 新版本……
    secondwtq
        11
    secondwtq  
       Nov 19, 2020
    你这代码根本就没法用来做 benchmark
    我记得 Java 做性能测试要用一个专门的库。JVM 会有一个 JIT 的策略,决定什么时候优化哪里的代码,不同的 JVM 版本这个可能会变。要测试性能,你得把这个拉平了。
    如果最后在同样的优化等级下还是有 regression 的话,那恭喜楼主可以给 JVM 贡献代码了。
    Cbdy
        12
    Cbdy  
       Nov 19, 2020
    刚刚简单了写了一个
    https://gist.github.com/cbdyzj/00bf90ae5def3609c3831b107ec89c21

    macOS Big Sur 11.0.1

    openjdk 15 2020-09-15
    OpenJDK Runtime Environment AdoptOpenJDK (build 15+36)
    OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15+36, mixed mode, sharing)

    --- n = 1000 ---

    Random Array
    InsertionSort: 0.004 s
    SelectionSort: 0.004 s

    Ordered Array
    InsertionSort: 0.0 s
    SelectionSort: 0.001 s

    --- n = 10000 ---

    Random Array
    InsertionSort: 0.042 s
    SelectionSort: 0.05 s

    Ordered Array
    InsertionSort: 0.0 s
    SelectionSort: 0.03 s

    --- n = 100000 ---

    Random Array
    InsertionSort: 1.367 s
    SelectionSort: 2.803 s

    Ordered Array
    InsertionSort: 0.0 s
    SelectionSort: 2.793 s
    oldshensheep
        13
    oldshensheep  
       Nov 19, 2020   ❤️ 1
    测试了一下,用的 jdk11,13,14,结果和楼主的一样。jdk8 的结果也和楼主一样。
    jdk8 的排序速度都差不多,jdk11,13,14 的插入和选择排序速度比是 1:2 。
    ![]( https://i.bmp.ovh/imgs/2020/11/b40a4e4ab9baf054.png)

    这可以说是负优化吗……,是由什么引起的呢?
    另外我上面的回复说的随机数种子的问题是我想错了……
    GTD
        14
    GTD  
    OP
       Nov 19, 2020 via iPhone
    @Cbdy 诶对啊,你这个差不多也是 1:2,这两个算法时间复杂度都是 o ( n ) 2 才对,不应该差距这么大
    Cbdy
        15
    Cbdy  
       Nov 19, 2020 via Android
    @GTD 时间复杂度 O(n^2)的意思是,随着 n 的增长,时间复杂度呈 n^2 增长,并不是两个相同时间复杂度的算法花费的时间是一样的

    比如两个算法,一个算法花的时间是 n,另一个是 10000n,他们的时间复杂度都是 O(n),但他们花费的时间相差一万倍

    另外,现代计算机其实对于交换和比较的耗时其实是有差别的,另外,不同排序算法对 CPU 缓存的命中率也会影响排序的速度

    我稍微看了一下你的代码,有个点注意一下,Java 数组本身就是协变的,没必要加那个协变泛型标注
    GTD
        16
    GTD  
    OP
       Nov 19, 2020
    @Cbdy #15

    不是啊,问题是为什么 jdk 11 以上,插入排序慢了一倍,而选择排序速度不变?跑了几遍都一样,这肯定跟算法和代码没关系
    GTD
        17
    GTD  
    OP
       Nov 19, 2020
    @Cbdy #15 不过谢谢你的回复,就很灵异,jdk 8 下两个算法不论跑多少遍,都约等于 1:1,到了 jdk 11 以上,两个算法不论跑多少遍,都约等于 1:2 。这个解释不通啊
    liuhuan475
        18
    liuhuan475  
       Nov 19, 2020
    等一手大佬回复
    Jooooooooo
        19
    Jooooooooo  
       Nov 19, 2020
    看下 GC 日志

    不同版本默认 GC 参数会有差异
    socket1q1
        20
    socket1q1  
       Nov 20, 2020
    等一手大佬回复
    acmore
        21
    acmore  
       Nov 20, 2020   ❤️ 1
    从 Java 9 开始默认 GC Collector 从 Parallel GC 切换为了 G1 。再根据你切换垃圾回收策略对结果的影响来看,GC 应该是主要问题,可以找找看不同策略的侧重和注意事项。
    securityCoding
        22
    securityCoding  
       Nov 20, 2020
    @secondwtq jmh,压测一定要用这个
    sagaxu
        23
    sagaxu  
       Nov 20, 2020 via Android   ❤️ 1
    就是 gc 不同,试试 zgc 和 epsilongc
    secondwtq
        24
    secondwtq  
       Nov 24, 2020   ❤️ 1
    分代 GC 需要加 Write Barrier 跟踪不同代对象之间的相互引用,G1 GC 相比 Parallel GC,Write Barrier 涉及的指令更多(实际并没有触发 GC,而是多执行了 10%-20% 的指令)。

    Epsilon GC 作为 placeholder,并不需要 Write Barrier 。但是直接用 Epsilon GC 效果并不会更好,原因应该是 C2 犯了傻逼,在 JIT 时把一个判断子类型的检查提到了外面,正常情况这个检查不会被触发,但是只要被触发很有可能失败,所以 JIT 的函数没法用,只能用 OSR 的。OSR 的循环并没有 JIT 更优化,特别是 CompressedOops 这时又占了很多指令,用 -XX:-UseProfiledLoopPredicate 可以把这个行为纠正回来( JDK 8 好像没有这个参数)。再加 -XX:-UseCompressedClassPointers 可以进一步减少指令数。
    (或者可以不用 -XX:-UseProfiledLoopPredicate,换成 -XX:-UseCompressedOops,但是这样还是会跑 OSR 循环,效果不如 JIT 好,尤其是变量 t 的访问没有优化)

    至于为啥倒霉的是插入排序,因为只有插入排序才需要在 inner loop 里面折腾引用值的赋值。别问,问就是天灭 Type Erasure,退 Java 保平安。祝楼主早日获得新生。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   3002 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 60ms · UTC 14:30 · PVG 22:30 · LAX 07:30 · JFK 10:30
    ♥ Do have faith in what you're doing.