【发布时间】:2019-08-31 01:40:21
【问题描述】:
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 的“标准”基准测试工具对这两种方法的数十亿次调用进行了基准测试。它足够接近,虽然 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
有什么理由更喜欢写一个而不是另一个,性能方面1?在某些情况下,它们的实施差异使一种比另一种更合适?或者,有谁知道为什么 javac 实现这两个相同的操作如此不同?
1 当然,我不会贸然利用这些信息进行微优化。我只是好奇这一切是如何运作的。
【问题讨论】:
-
引入测试和分支显然会对性能产生一些影响。多少取决于多种因素,其中最重要的是该分支的可预测性。关于这个问题的大量现有技术;我会无耻地插入my own answer 作为起点。我无法发布实际答案,因为我不熟悉 Java 字节码是如何被翻译成机器码的。中间是否有优化器?可能是。无论哪种方式,都要提防过早的微优化。先写代码说出你的意思。
-
p != q建议使用比较指令,而p ^ q建议使用xor指令。这就是你在字节码中看到的。如果以这种自然的方式进一步编译为机器码,那么p ^ q如果将结果用作数字或存储到内存中可能会快一些,但如果用作分支条件会稍微慢一些。 -
为什么
p ^ q会“如果用作分支条件会稍微慢一些”,@zch? -
@CodyGray 实际上,从字节码进行的翻译很复杂,并且涉及到优化器。通常,字节码会被解释一段时间,并且只有在确定为运行时性能热点时才会将其 JIT 编译为本机代码。 JIT 优化器可以使用运行时信息来指导它的优化——我不是专家,但我想它可以使用它来指导它的分支预测,例如。这是 JVM 基准测试像标准一样“预热 JIT”很重要的原因之一。
-
@CodyGray,但如果编译器使用
xor并且它直接作为标志,在某些情况下它仍然会破坏优化,因为它会改变保存p(或q)的寄存器。跨度>
标签: java performance