为什么String.equals()比自身更快?

时间:2013-03-23 12:52:30

标签: java optimization

我试图创建一个更快版本的String.equals()方法,并开始只需复制它。我发现的结果非常令人困惑。当我运行复制粘贴版本,定时并将其与JVM版本进行比较时,JVM版本更快。差异从6倍到34倍不等!简单地说,字符串越长,差异越大。

    boolean equals(final char a[], final char b[]) {
    int n = a.length;
    int i = 0;

    while (n-- != 0) {
        if (a[i] != b[i]) return false;
        i++;
    }
    return true;
}

public static void main() throws Exception {
    String a = "blah balh balh";
    String b = "blah balh balb";

    long me = 0, jvm = 0;

    Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);

    final char lhs[] = (char[]) value.get(a);
    final char rhs[] = (char[]) value.get(b);
    for (int i = 0; i < 100; i++) {
        long t = System.nanoTime();
        equals(lhs, rhs);
        t = System.nanoTime() - t;
        me += t;
    }

    for (int i = 0; i < 100; i++) {
        long t = System.nanoTime();
        a.equals(b);
        t = System.nanoTime() - t;
        jvm += t;
    }

    System.out.println("me  = " + me);
    System.out.println("jvm = " + jvm);
}

输出:

me  = 258931
jvm = 14991

我写的equals()方法是String.equals()方法中找到的版本的复制粘贴版本。为什么JVM版本比它的复制粘贴版本更快。是不是真的一样?

有人可以解释为什么我会看到这种明显的差异吗?

PS:如果你希望看到很大的差异,你可以创建长(真的,非常长)的字符串,最后只有一个字符不同。

3 个答案:

答案 0 :(得分:15)

  

为什么JVM版本比它的复制粘贴版本更快。是不是真的一样?

令人惊讶的是,事实并非如此。

字符串比较是一种无处不在的操作,几乎可以肯定的是,JIT compiler具有String.equals()的内在函数。这意味着编译器知道如何生成用于比较字符串的特制机器代码。当您使用String.equals()时,程序员可以透明地完成此操作。

这可以解释为什么String.equals()比你的方法快得多,即使表面看起来相同也是如此。

快速搜索找到几个在HotSpot中提到这种内在的错误报告。例如,7041100 : The load in String.equals intrinsic executed before null check

可以找到相关的HotSpot来源here。有问题的函数是:

  848 Node* LibraryCallKit::make_string_method_node(int opcode, Node* str1, Node* cnt1, Node* str2, Node* cnt2) {

  943 bool LibraryCallKit::inline_string_equals() {

答案 1 :(得分:2)

Hotspot允许开发人员除了Java实现之外还提供方法的本机实现。 Java代码在运行时被换出,并被优化版本替换。它被称为内在的。基类中几百种方法都是由内在函数优化的。

通过查看OpenJDK源代码,您可以see the x86_64 implementation of String.equals。您还可以查看vmSymbols以获取所有内容列表(搜索do_intrinsic

答案 2 :(得分:-1)

您知道, JAVA确实既不是基于编译器的语言也不是基于解释器的语言。我的意思是说Java同时使用了这两种方法,即编译器将源代码转换为中间形式,然后在运行时进行解释。

它标记了执行的LoC(代码行),以了解代码的哪一部分被更频繁地运行。 JAVA一旦发现一部分代码运行超过给定的阈值,就会将其标记为热,并将此代码段即时发送给编译器,以便在下次请求时运行更好的版本。这称为 JIT(准时生产)

现在,由于这两个代码完全相似,因此JAVA HotSpot应该对这两种方法进行了完全相同的优化,因此执行时间也相同。可悲的是,事实并非如此。

JIT马上发现 equals()方法很热,而且调用频率太高,因此需要在硬件级别进行特殊优化。
是的,英特尔创建了一组全新的指令来加速文本处理代码。这意味着,有一条单独的指令可以使 Strings.equals()方法比复制粘贴的equals方法更快。

现在的问题是这是如何发生的。好吧,这很简单,只要 String.equals()暖和的(即,使用频率更高但使用频率不高),编译器就会执行与复制粘贴方法相同的优化。但是随着 equal()方法变得,JIT直接使用 new指令设置进行字符串比较,从而快速执行。