gpt4 book ai didi

java - 为什么带有两个常量的三元运算符比带有变量的三元运算符快?

转载 作者:行者123 更新时间:2023-12-01 09:00:48 25 4
gpt4 key购买 nike

在Java中,我有两个不同的语句,它们通过使用三元运算符来完成相同的结果,如下所示:

  • num < 0 ? 0 : num;
  • num * (num < 0 ? 0 : 1);

  • 看起来第二个语句不必要地复杂并且比第一个语句花费的时间更长,但是当我使用以下代码记录每个花费的时间时,结果如下:
    final long startTime = System.currentTimeMillis();

    Random rand = new Random();
    float[] results = new float[100000000];
    for (int i = 0; i < 100000000; i++) {
    float num = (rand.nextFloat() * 2) - 1;
    results[i] = num < 0 ? 0 : num;
    //results[i] = num * (num < 0 ? 0 : 1);
    }

    final long endTime = System.currentTimeMillis();

    System.out.println("Total Time: " + (endTime - startTime));
  • 1.232 秒
  • 1.023 秒
    (每个平均超过 5 次)

  • 为什么在使用第二个语句时会有如此显着的加速?它似乎包括一个不必要的乘法并具有相同的比较。第一个是否创建一个分支而第二个没有?

    最佳答案

    首先,让我们用 JMH 重写基准测试避免 common benchmarking pitfalls .

    public class FloatCompare {

    @Benchmark
    public float cmp() {
    float num = ThreadLocalRandom.current().nextFloat() * 2 - 1;
    return num < 0 ? 0 : num;
    }

    @Benchmark
    public float mul() {
    float num = ThreadLocalRandom.current().nextFloat() * 2 - 1;
    return num * (num < 0 ? 0 : 1);
    }
    }
    JMH 还建议乘法代码更快:
    Benchmark         Mode  Cnt   Score   Error  Units
    FloatCompare.cmp avgt 5 12,940 ± 0,166 ns/op
    FloatCompare.mul avgt 5 6,182 ± 0,101 ns/op
    现在是时候参与 perfasm profiler (内置在 JMH 中)以查看 JIT 编译器生成的程序集。以下是输出中最重要的部分(评论是我的): cmp方法:
      5,65%  │││  0x0000000002e717d0: vxorps  xmm1,xmm1,xmm1  ; xmm1 := 0
    0,28% │││ 0x0000000002e717d4: vucomiss xmm1,xmm0 ; compare num < 0 ?
    4,25% │╰│ 0x0000000002e717d8: jbe 2e71720h ; jump if num >= 0
    9,77% │ ╰ 0x0000000002e717de: jmp 2e71711h ; jump if num < 0
    mul方法:
      1,59%  ││  0x000000000321f90c: vxorps  xmm1,xmm1,xmm1    ; xmm1 := 0
    3,80% ││ 0x000000000321f910: mov r11d,1h ; r11d := 1
    ││ 0x000000000321f916: xor r8d,r8d ; r8d := 0
    ││ 0x000000000321f919: vucomiss xmm1,xmm0 ; compare num < 0 ?
    2,23% ││ 0x000000000321f91d: cmovnbe r11d,r8d ; r11d := r8d if num < 0
    5,06% ││ 0x000000000321f921: vcvtsi2ss xmm1,xmm1,r11d ; xmm1 := (float) r11d
    7,04% ││ 0x000000000321f926: vmulss xmm0,xmm1,xmm0 ; multiply
    关键区别在于 mul中没有跳转指令。方法。相反,条件移动指令 cmovnbe用来。 cmov适用于整数寄存器。自 (num < 0 ? 0 : 1)表达式在右侧使用整数常量,JIT 足够聪明,可以发出条件移动而不是条件跳转。
    在这个基准测试中,条件跳转非常低效,因为 branch prediction由于数字的随机性,经常失败。这就是为什么 mul 的无分支代码方法出现更快。
    如果我们以一个分支优于另一个分支的方式修改基准,例如通过替换
    ThreadLocalRandom.current().nextFloat() * 2 - 1
    ThreadLocalRandom.current().nextFloat() * 2 - 0.1f
    那么分支预测会更好,并且 cmp方法将变得和 mul 一样快:
    Benchmark         Mode  Cnt  Score   Error  Units
    FloatCompare.cmp avgt 5 5,793 ± 0,045 ns/op
    FloatCompare.mul avgt 5 5,764 ± 0,048 ns/op

    关于java - 为什么带有两个常量的三元运算符比带有变量的三元运算符快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62412194/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com