为了账号安全,请及时绑定邮箱和手机立即绑定

对于布尔值,(p ^ q)和(p != q)之间是否有有用的区别?

对于布尔值,(p ^ q)和(p != q)之间是否有有用的区别?

九州编程 2022-09-28 10:14:09
Java有两种方法可以检查两个布尔值是否不同。您可以将它们与 或 (xor) 进行比较。当然,这两个运算符在所有情况下都会产生相同的结果。尽管如此,将它们都包括在内是有道理的,例如,在XOR和不等于之间的区别是什么?对于开发人员来说,根据上下文更喜欢一个而不是另一个甚至有意义 - 有时“恰好是这些布尔值中的一个吗”读起来更好,而其他时候“这两个布尔值是否不同”更好地传达意图。所以,也许使用哪一个应该是品味和风格的问题。!=^令我惊讶的是,javac并没有以相同的方式对待这些!请考虑此类:class Test {  public boolean xor(boolean p, boolean q) {    return p ^ q;  }  public boolean inequal(boolean p, boolean q) {    return p != q;  }}显然,这两种方法具有相同的可见行为。但它们有不同的字节码:$ javap -c TestCompiled from "Test.java"class Test {  Test();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."<init>":()V       4: return  public boolean xor(boolean, boolean);    Code:       0: iload_1       1: iload_2       2: ixor       3: ireturn  public boolean inequal(boolean, boolean);    Code:       0: iload_1       1: iload_2       2: if_icmpeq     9       5: iconst_1       6: goto          10       9: iconst_0      10: ireturn}如果我不得不猜测,我会说它的表现更好,因为它只是返回其比较的结果;添加跳跃和额外的负载似乎只是浪费工作。但是,我没有猜测,而是使用 Clojure 的“criterium”基准测试工具对这两种方法进行了数十亿次调用的基准测试。它足够接近,虽然看起来xor有点快,但我在统计数据方面不够好,无法说明结果是否显着:xoruser=> (let [t (Test.)] (bench (.xor t true false)))Evaluation count : 4681301040 in 60 samples of 78021684 calls.             Execution time mean : 4.273428 ns    Execution time std-deviation : 0.168423 ns   Execution time lower quantile : 4.044192 ns ( 2.5%)   Execution time upper quantile : 4.649796 ns (97.5%)                   Overhead used : 8.723577 ns有没有理由更喜欢写一个而不是另一个,性能方面1?在某种情况下,它们的实现差异使一个比另一个更合适?或者,有谁知道为什么javac实现这两个相同的操作如此不同?1 当然,我不会鲁莽地利用这些信息进行微优化。我只是好奇这一切是如何工作的。
查看完整描述

1 回答

?
肥皂起泡泡

TA贡献1829条经验 获得超6个赞

好吧,我将很快提供CPU如何转换并更新帖子,但与此同时,您正在查看太小的差异而无法关心。


Java中的字节码并不表示方法的执行速度(或不执行),有两个JIT编译器一旦足够热,它们将使此方法看起来完全不同。众所周知,一旦编译代码,就会进行很少的优化,真正的优化来自。javacJIT


我已经为此进行了一些测试,要么只使用编译器,要么用替换,要么根本不使用...(下面有很多测试代码,你可以跳过它,只看结果,这是使用btw完成的)。此代码使用的是JMH - 在微基准测试的java世界中使用的事实上的工具(如果手动完成,则容易出错)。JMHC1C2GraalVMJITjdk-12


@Warmup(iterations = 10)

@OutputTimeUnit(TimeUnit.NANOSECONDS)

@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)

public class BooleanCompare {


    public static void main(String[] args) throws Exception {

        Options opt = new OptionsBuilder()

            .include(BooleanCompare.class.getName())

            .build();


        new Runner(opt).run();

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(1)

    public boolean xor(BooleanExecutionPlan plan) {

        return plan.booleans()[0] ^ plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(1)

    public boolean plain(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1, jvmArgsAppend = "-Xint")

    public boolean xorNoJIT(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1, jvmArgsAppend = "-Xint")

    public boolean plainNoJIT(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1, jvmArgsAppend = "-XX:-TieredCompilation")

    public boolean xorC2Only(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1, jvmArgsAppend = "-XX:-TieredCompilation")

    public boolean plainC2Only(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1, jvmArgsAppend = "-XX:TieredStopAtLevel=1")

    public boolean xorC1Only(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1, jvmArgsAppend = "-XX:TieredStopAtLevel=1")

    public boolean plainC1Only(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1,

        jvmArgsAppend = {

            "-XX:+UnlockExperimentalVMOptions",

            "-XX:+EagerJVMCI",

            "-Dgraal.ShowConfiguration=info",

            "-XX:+UseJVMCICompiler",

            "-XX:+EnableJVMCI"

        })

    public boolean xorGraalVM(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


    @Benchmark

    @BenchmarkMode(Mode.AverageTime)

    @Fork(value = 1,

        jvmArgsAppend = {

            "-XX:+UnlockExperimentalVMOptions",

            "-XX:+EagerJVMCI",

            "-Dgraal.ShowConfiguration=info",

            "-XX:+UseJVMCICompiler",

            "-XX:+EnableJVMCI"

        })

    public boolean plainGraalVM(BooleanExecutionPlan plan) {

        return plan.booleans()[0] != plan.booleans()[1];

    }


}

结果:


BooleanCompare.plain         avgt    2    3.125          ns/op

BooleanCompare.xor           avgt    2    2.976          ns/op


BooleanCompare.plainC1Only   avgt    2    3.400          ns/op

BooleanCompare.xorC1Only     avgt    2    3.379          ns/op


BooleanCompare.plainC2Only   avgt    2    2.583          ns/op

BooleanCompare.xorC2Only     avgt    2    2.685          ns/op


BooleanCompare.plainGraalVM  avgt    2    2.980          ns/op

BooleanCompare.xorGraalVM    avgt    2    3.868          ns/op


BooleanCompare.plainNoJIT    avgt    2  243.348          ns/op

BooleanCompare.xorNoJIT      avgt    2  201.342          ns/op

我不是一个多才多艺的人来阅读汇编程序,尽管我有时喜欢这样做......这里有一些有趣的事情。如果我们这样做:


C1 编译器仅包含 !=


/*

 * run many iterations of this with :

 *  java -XX:+UnlockDiagnosticVMOptions  

 *       -XX:TieredStopAtLevel=1  

 *       "-XX:CompileCommand=print,com/so/BooleanCompare.compare"  

 *       com.so.BooleanCompare

 */

public static boolean compare(boolean left, boolean right) {

    return left != right;

}

我们得到:


  0x000000010d1b2bc7: push   %rbp

  0x000000010d1b2bc8: sub    $0x30,%rsp  ;*iload_0 {reexecute=0 rethrow=0 return_oop=0}

                                         ; - com.so.BooleanCompare::compare@0 (line 22)


  0x000000010d1b2bcc: cmp    %edx,%esi

  0x000000010d1b2bce: mov    $0x0,%eax

  0x000000010d1b2bd3: je     0x000000010d1b2bde

  0x000000010d1b2bd9: mov    $0x1,%eax

  0x000000010d1b2bde: and    $0x1,%eax

  0x000000010d1b2be1: add    $0x30,%rsp

  0x000000010d1b2be5: pop    %rbp

对我来说,这个代码有点明显:把0放进去,->如果不等于的话,把1放进去。返回。eaxcompare (edx, esi)eaxeax & 1


带有 ^的 C1 编译器:


public static boolean compare(boolean left, boolean right) {

     return left ^ right;

}




  # parm0:    rsi       = boolean

  # parm1:    rdx       = boolean

  #           [sp+0x40]  (sp of caller)

  0x000000011326e5c0: mov    %eax,-0x14000(%rsp)

  0x000000011326e5c7: push   %rbp

  0x000000011326e5c8: sub    $0x30,%rsp   ;*iload_0 {reexecute=0 rethrow=0 return_oop=0}

                                          ; - com.so.BooleanCompare::compare@0 (line 22)


  0x000000011326e5cc: xor    %rdx,%rsi

  0x000000011326e5cf: and    $0x1,%esi

  0x000000011326e5d2: mov    %rsi,%rax

  0x000000011326e5d5: add    $0x30,%rsp

  0x000000011326e5d9: pop    %rbp

我真的不知道为什么这里需要,否则我想这也相当简单。and $0x1,%esi


但是如果我启用C2编译器,事情会更有趣。


/**

 * run with java

 * -XX:+UnlockDiagnosticVMOptions

 * -XX:CICompilerCount=2

 * -XX:-TieredCompilation

 * "-XX:CompileCommand=print,com/so/BooleanCompare.compare"

 * com.so.BooleanCompare

 */

public static boolean compare(boolean left, boolean right) {

    return left != right;

}




  # parm0:    rsi       = boolean

  # parm1:    rdx       = boolean

  #           [sp+0x20]  (sp of caller)

  0x000000011a2bbfa0: sub    $0x18,%rsp

  0x000000011a2bbfa7: mov    %rbp,0x10(%rsp)                


  0x000000011a2bbfac: xor    %r10d,%r10d

  0x000000011a2bbfaf: mov    $0x1,%eax

  0x000000011a2bbfb4: cmp    %edx,%esi

  0x000000011a2bbfb6: cmove  %r10d,%eax                     


  0x000000011a2bbfba: add    $0x10,%rsp

  0x000000011a2bbfbe: pop    %rbp

我甚至没有看到经典的后记,而是通过以下方式看到一些非常不寻常的东西(至少对我来说):push ebp; mov ebp, esp; sub esp, x


 sub    $0x18,%rsp

 mov    %rbp,0x10(%rsp)


 ....

 add    $0x10,%rsp

 pop    %rbp

再一次,比我更全能的人可以解释。否则,它就像一个更好的版本:C1


xor    %r10d,%r10d // put zero into r10d

mov    $0x1,%eax   // put 1 into eax

cmp    %edx,%esi   // compare edx and esi

cmove  %r10d,%eax  // conditionally move the contents of r10d into eax

AFAIK比因为分支预测更好 - 这至少是我读过的......cmp/cmovecmp/je


使用 C2 编译器的异或:


public static boolean compare(boolean left, boolean right) {

    return left ^ right;

}




  0x000000010e6c9a20: sub    $0x18,%rsp

  0x000000010e6c9a27: mov    %rbp,0x10(%rsp)                


  0x000000010e6c9a2c: xor    %edx,%esi

  0x000000010e6c9a2e: mov    %esi,%eax

  0x000000010e6c9a30: and    $0x1,%eax

  0x000000010e6c9a33: add    $0x10,%rsp

  0x000000010e6c9a37: pop    %rbp

它看起来确实与编译器生成的几乎相同。C1


查看完整回答
反对 回复 2022-09-28
  • 1 回答
  • 0 关注
  • 108 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号