(p^q)和(p!=q)对于布尔人有什么有用的区别吗?
Java有两种检查两个布尔值是否不同的方法。您可以将它们与!=
或^
(xor)进行比较。当然,这两个操作符在所有情况下都会产生相同的结果。尽管如此,如在What's the difference between XOR and NOT-EQUAL-TO?中所讨论的那样,将它们都包括在内是有道理的。开发人员甚至可以根据上下文选择其中一个,有时“这两个布尔值中有一个是真的”读得更好,而其他时候“这两个布尔值不同”读得更好。所以,使用哪一种应该是品味和风格的问题
令我惊讶的是,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 Test
Compiled 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
}
如果要我猜的话,我会说xor
性能更好,因为它只返回比较的结果;再加上一个跳跃和一个额外的负载似乎是白费力气。但我没有猜测,而是使用Clojure的“criterium”基准测试工具对这两种方法的数十亿次调用进行了基准测试。虽然xor看起来快了一点,但我不太擅长统计,无法判断结果是否重要:
user=> (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
Found 2 outliers in 60 samples (3.3333 %)
low-severe 2 (3.3333 %)
Variance from outliers : 25.4745 % Variance is moderately inflated by outliers
user=> (let [t (Test.)] (bench (.inequal t true false)))
Evaluation count : 4570766220 in 60 samples of 76179437 calls.
Execution time mean : 4.492847 ns
Execution time std-deviation : 0.162946 ns
Execution time lower quantile : 4.282077 ns ( 2.5%)
Execution time upper quantile : 4.813433 ns (97.5%)
Overhead used : 8.723577 ns
Found 2 outliers in 60 samples (3.3333 %)
low-severe 2 (3.3333 %)
Variance from outliers : 22.2554 % Variance is moderately inflated by outliers
在性能方面,有什么理由更喜欢写一个而不是另一个吗?在某种情况下,它们在实现上的差异使一个比另一个更合适?或者,有人知道为什么javac实现这两个相同的操作如此不同吗
1当然,我不会鲁莽地使用这些信息进行微优化。我只是好奇这一切是如何运作的
# 1 楼答案
好吧,我将很快提供CPU如何翻译这一点并更新帖子,但与此同时,您看到的waaaay差异太小,无法关注
java中的字节码并不能表示一个方法的执行速度,有两个JIT编译器,一旦它们足够热,就会使这个方法看起来完全不同。另外
javac
已知在编译代码后只做很少的优化,真正的优化来自JIT
我已经用
JMH
进行了一些测试,只使用C1
编译器,或者用GraalVM
替换C2
,或者根本不使用JIT
。。。(下面有很多测试代码,您可以跳过它,只需查看结果,这是使用jdk-12
btw完成的)。这段代码使用的是JMH,这是java世界中使用的事实上的微型基准测试工具(如果手工操作,则极易出错)结果是:
我不是一个多才多艺的人,不会阅读汇编程序,尽管我有时喜欢这样做。。。以下是一些有趣的事情。如果我们这样做:
我们得到:
对我来说,这段代码有点明显:将0放入
eax
,compare (edx, esi)
->;如果不相等,则将1放入eax
。返回eax & 1
我真的不知道为什么这里需要
and $0x1,%esi
,否则我想这也相当简单我甚至没有看到经典的尾声
push ebp; mov ebp, esp; sub esp, x
,而是通过以下方式看到一些非常不寻常的东西(至少对我而言):再说一次,一个比我多才多艺的人,可以满怀希望地解释。否则它就像是生成的
C1
的更好版本:因为分支预测,AFAIK
cmp/cmove
比cmp/je
好——至少我读到了这一点它看起来和编译器生成的
C1
几乎一样