亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

對于布爾值,(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 關注
  • 127 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號