17

我试图创建一个更快版本的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:如果您希望看到较大的差异,您可以创建长(真的,真的很长)字符串,最后只有一个字符不同。

4

3 回答 3

17

为什么 JVM 版本比复制粘贴的版本快。效果不一样吗?

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

字符串比较是一种无处不在的操作,几乎可以肯定您的JIT 编译器具有内在的 for String.equals(). 这意味着编译器知道如何生成特制的机器代码来比较字符串。当您使用String.equals().

这可以解释为什么String.equals()比您的方法快得多,即使表面上它们看起来相同。

快速搜索会发现几个在 HotSpot 中提到这种内在特性的错误报告。例如,7041100 : String.equals 中的负载在 null check 之前执行

可以在此处找到相关的 HotSpot 源。有问题的功能是:

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

  943 bool LibraryCallKit::inline_string_equals() {
于 2013-03-23T13:19:06.843 回答
2

Hotspot 允许开发人员在 Java 实现之外提供方法的本机实现。Java 代码在运行时被换出并替换为优化版本。它被称为内在。数百种来自基类的方法通过内在函数进行了优化。

通过查看 OpenJDK 源代码,您可以看到 String.equals 的 x86_64 实现。您还可以查看vmSymbols以获取所有内在函数的列表(搜索do_intrinsic

于 2013-03-23T13:40:15.707 回答
-1

如您所知,JAVA 确实既不是基于编译器也不是基于解释器的语言。我的意思是java同时使用编译器将源代码转换为中间形式,然后在运行时进行解释。

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

,因为这两个代码完全相似,JAVA HotSpot 应该优化这两个方法完全相同,因此执行时间相同。可悲的是,事实并非如此。

一旦 JIT 发现equals()方法很热并且被过于频繁地调用,它就会在硬件级别进行特殊优化。
是的,英特尔创建了一套全新的指令来加速文本处理代码。这意味着,有一个单独的指令可以使Strings.equals()方法比复制粘贴的 equals 方法更快。

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

于 2018-07-29T18:02:24.393 回答