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

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

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

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

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

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

快速搜索找到几个在HotSpot中提到这种内在的错误报告。 例如, 7041100:在null检查之前执行String.equals内部的加载 。

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

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

  943 bool LibraryCallKit::inline_string_equals() { 

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

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

如您所知,JAVA既不是基于编译器的语言,也不是基于解释器的语言。 它标记了执行的LoC(代码行),以便更频繁地运行代码的哪一部分。 一旦JAVA计算出运行超过给定阈值的代码的一部分,它就会标记它并将这段代码动态发送给编译器,以便在下次询问时运行更好的优化版本。 这称为JIT(及时)

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

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

现在的问题是这是如何发生的。 嗯,这很简单,每当String.equals() (即更常使用但不大)时,编译器会执行与复制粘贴方法相同的优化。 但是当equal()方法变热时 ,JIT直接使用新的指令集进行字符串比较,从而快速执行。