Based on my tests with a few different Oracle and OpenJDK implementations, it seems that Arrays.equals(char[], char[]) is somehow about 8 times fas
Because for chars, SSE3 and 4.1/4.2 are both extremely good at checking state change. The JVM-generated code for char manipulation code is more tuned because that's what Java gets used a lot for in web apps and such. Java is awful at optimizing for other kinds of data. That's just the nature of the beast.
This same behaviour is observable in Scala and GoSu as well. The bulk of information in transit these days is in text form, so unless you modify your JVM, it's tuned for text. And, as Marco mentioned, it is an intrinsic C function underneath, meaning it directly maps to high-performance vectorized instructions like SSE4.x or even AVX2 if the standard JVM has been improved that much.
http://blog.synopse.info/post/2015/06/30/Faster-String-process-using-SSE-4.2-Text-Processing-Instructions-STTNI
http://www.tomshardware.com/reviews/Intel-i7-nehalem-cpu,2041-7.html
Seriously, SSE4.x does not treat chars and bytes as equivalent data types, which is why text analysis is faster. Furthermore, for 8-bit integral, comparisons the instructions didn't exist until AVX2.
I might go out on a limb when suggesting that this is the answer, but according to http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/9d15b81d5d1b/src/share/vm/classfile/vmSymbols.hpp#l756, the Arrays#equals(char[], char[])
method is implemented as an intrinsic.
Most likely because it is highly performance critical in all string comparisons. <- This was wrong, at least. Surprisingly, String does not use Arrays.equals
for the comparison. But regardless of why it is an intrinsic, this may still be the reason for the performance difference.
@Marco13 guess was right. HotSpot JVM has an intrinsic (i.e. special hand-coded implementation) forArrays.equals(char[], char[])
, but not for other Arrays.equals
methods.
The following JMH benchmark proves that disabling this intrinsic makes char[]
array comparsion as slow as short[]
array comparison.
@State(Scope.Benchmark)
public class ArrayEquals {
@Param("100")
int length;
short[] s1, s2;
char[] c1, c2;
@Setup
public void setup() {
s1 = new short[length];
s2 = new short[length];
c1 = new char[length];
c2 = new char[length];
}
@Benchmark
public boolean chars() {
return Arrays.equals(c1, c2);
}
@Benchmark
@Fork(jvmArgsAppend = {"-XX:+UnlockDiagnosticVMOptions", "-XX:DisableIntrinsic=_equalsC"})
public boolean charsNoIntrinsic() {
return Arrays.equals(c1, c2);
}
@Benchmark
public boolean shorts() {
return Arrays.equals(s1, s2);
}
}
Results:
Benchmark (length) Mode Cnt Score Error Units
ArrayEquals.chars 100 avgt 10 19,012 ± 1,204 ns/op
ArrayEquals.charsNoIntrinsic 100 avgt 10 49,495 ± 0,682 ns/op
ArrayEquals.shorts 100 avgt 10 49,566 ± 0,815 ns/op
This intrinsic was added long ago in 2008 in the times of aggressive JVM competition. JDK 6 included a special alt-string.jar
library which was enabled by -XX:+UseStringCache
. I've found a number of calls to Arrays.equals(char[], char[])
from one of these special classes - StringValue.StringCache
. The intrinsic was an essential part of this "optimization". In modern JDK there is no more alt-string.jar
, but JVM intrinsic is still there (not playing its original role though).
Update
I've tested the same with JDK 9-ea+148, and it appears that _equalsC
intrinsic makes very little performance difference.
Benchmark (length) Mode Cnt Score Error Units
ArrayEquals.chars 100 avgt 10 18,931 ± 0,061 ns/op
ArrayEquals.charsNoIntrinsic 100 avgt 10 19,616 ± 0,063 ns/op
ArrayEquals.shorts 100 avgt 10 19,753 ± 0,080 ns/op
Arrays.equals
implementation has changed in JDK 9.
Now it calls ArraysSupport.vectorizedMismatch helper method for all types of non-object arrays. Furthermore, vectorizedMismatch
is also a HotSpot intrinsic which has hand-written assembly implementation that uses AVX.