I was attempting to create a faster version of String.equals() method and started by simply copying it. The result I found was quite confusing. When I ran the copy pasted versio
Hotspot allows developers to provide a native implementation of a method in addition of the Java implementation. The Java code is swapped out at runtime and replaced by the optimized version. It is called an intrinsic. Few hundred of methods from base classes are optimized by intrinsics.
By looking at the OpenJDK source code you can see the x86_64 implementation of String.equals. You can also look into vmSymbols to get the list of all instrinsics (search for do_intrinsic
)
As you know, JAVA truly is neither a compiler based nor interpreter based language. By this I mean is java uses both i.e. a compiler to convert source code into an intermediate form which is then interpreted at runtime.
It marks the LoC (Line of Code) executed to know which part of the code is run more frequently. As soon as JAVA figures out a part of code running more than a given threshold, it marks it hot and sends this piece of code to the compiler on the fly for a better-optimized version to be run when asked for the next time. This is called JIT (Just in Time)
Now, since both the code are exactly similar, JAVA HotSpot should have optimized both the methods exactly the same and hence same execution time. Sadly, this isn't the case.
As soon as, JIT figures out equals() method is hot and is being called too frequently, it goes for a special optimization at the hardware level.
Yes, There is an entirely new set of instructions created by Intel to speed up text processing code. This means, there is a separate instruction to make Strings.equals() method more faster than your copy-pasted equals method.
Now the question is how does this happen. Well, this is simple, Whenever String.equals() is warm (i.e. used more often but not heavily) the compiler does the same optimization as with the copy-pasted method. But as the equal() method gets hot, JIT directly uses the new instruction set for string comparison and Hence fast execution.
Why is the JVM version faster than it's copy-pasted version. Isn't it effectively the same?
Surprisingly, it isn't.
String comparison is such an ubiquitous operation that it is almost certainly the case that your JIT compiler has an intrinsic for String.equals()
. This means that the compiler knows how to generate specially-crafted machine code for comparing strings. This is done transparently to you, the programmer, when you use String.equals()
.
This would explain why String.equals()
is so much faster than your method, even if superficially they appear identical.
A quick search finds several bug reports that mention such an intrinsic in HotSpot. For example, 7041100 : The load in String.equals intrinsic executed before null check.
The relevant HotSpot source can be found here. The functions in question are:
848 Node* LibraryCallKit::make_string_method_node(int opcode, Node* str1, Node* cnt1, Node* str2, Node* cnt2) {
and
943 bool LibraryCallKit::inline_string_equals() {