为什么 String.equals() 比它本身快?

2022-09-03 02:04:26

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


答案 1

为什么 JVM 版本比复制粘贴版本快。它实际上不是一样的吗?

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

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

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

快速搜索会发现几个错误报告,其中提到了HotSpot中的这种内在功能。例如,7041100:String.等于在空值检查之前执行的内部函数中的负载

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

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

  943 bool LibraryCallKit::inline_string_equals() {

答案 2

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

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


推荐