Why is Arrays.equals(char[], char[]) 8 times faster than all the other versions?
<h3>Short Story</h3>

<p>Based on my tests with a few different Oracle and OpenJDK implementations, it seems that <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#equals-char:A-char:A-" rel="noreferrer"><code>Arrays.equals(char[], char[])</code></a> is somehow about <strong>8 times faster</strong> than all the other variants for other types. </p>

<p><a href="https://i.stack.imgur.com/dXnDU.png" rel="noreferrer"><img src="https://i.stack.imgur.com/dXnDU.png" alt="Figure 1"></a></p>

<p>If the performance of your application is correlated heavily<sup>0</sup> with comparing arrays for equality, this means you pretty much want to coerce all your data into <code>char[]</code>, just to get this <em>magic</em> performance boost.</p>

<h3>Long Story</h3>

<p>Recently I was writing some high-performance code that used <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#equals-byte:A-byte:A-" rel="noreferrer"><code>Arrays.equals(...)</code></a> to compare keys used to index into a structure. The keys can be quite long, and often differ only in later bytes, so the performance of this method is pretty important.</p>

<p>At one point I was using keys of type <code>char[]</code>, but as part of generalizing the service and to avoid some copies from underlying sources of <code>byte[]</code> and <code>ByteBuffer</code>, I changed this to <code>byte[]</code>. Suddenly<sup>2</sup>, the performance of many basic operations dropped by about 3x. I traced it down the above-mentioned fact: <code>Arrays.equals(char[], char[])</code> seems to enjoy a special status over all the other <code>Arrays.equals()</code> versions, including the one taking <code>short[]</code> which is semantically identical (and could be implemented with the identical underlying code since signedness doesn't affect the behavior of equals).</p>

<p>So I wrote a <a href="https://gist.github.com/travisdowns/c7bebc4176f079ca76f3ad9dc434a776" rel="noreferrer">JMH benchmark</a> to test all the primitive variants of <code>Arrays.equals(...)</code><sup>1</sup> and the <code>char[]</code> variant crushes all the others, as shown above. </p>

<p>Now, this dominance of the ~8x variety doesn't extend at the same magnitude to smaller or much larger arrays - but it is still faster.</p>

<p>For small arrays, it seems that constant factors start to dominate, and for larger arrays, the L2/L3 or main memory bandwidth starts to come into play (you can already see the latter effect pretty clearly in the earlier figure, where <code>int[]</code> and especially <code>long[]</code> arrays start to degrade in performance at the large sizes). Here's a look at the same test, but with a smaller small array and larger large array:</p>

<p><a href="https://i.stack.imgur.com/PtToq.png" rel="noreferrer"><img src="https://i.stack.imgur.com/PtToq.png" alt="Figure 2"></a> </p>

<p>Here, <code>char[]</code> is still kicking ass, just not as much as before. The per-element times for the small array (only 16 elements) are about double the standard time, probably due to function overheads: at around 0.5 ns/element, the <code>char[]</code> variant still only takes about 7.2 nanoseconds for the whole call, or about 19 cycles on my machine - so a small amount of method overhead cuts into the runtime a lot (also, the benchmark overhead itself is several cycles).</p>

<p>At the big end, cache and/or memory bandwidth is a driving factor - the <code>long[]</code> variant takes almost 2x as long as the <code>int[]</code> variant. The <code>short[]</code> and especially the <code>byte[]</code> variant aren't very effective (their working set still fits in the L3 in my machine).</p>

<p>The difference between <code>char[]</code> and all the others is extreme enough that for applications that rely on array comparison (this isn't actually that unusual for some specific domains), it's worth it to try to get all your data into <code>char[]</code> to take advantage. Huh.</p>

<p>What gives? Does <code>char</code> get special treatment because it underlies some <code>String</code> methods? Is it just another case of the JVM optimizing methods that are hit heavily in benchmarks, and not extending the same (obvious) optimizations to the other primitive types (especially <code>short</code> which is <em>identical</em> here)?</p>

<hr>

<p><sup>0</sup> ... and that's not even all that crazy - consider various systems which rely, for example, on (lengthy) hash comparison to check if values are equal, or hash maps where the keys are either long or variably sized.</p>

<p><sup>1</sup>I didn't include  <code>boolean[]</code>, <code>float[]</code> and <code>double[]</code> or double in the results to avoid cluttering the graph, but for the record <code>boolean[]</code> and <code>float[]</code> performed the same as <code>int[]</code>, while <code>double[]</code> performed the same as <code>long[]</code>. That makes sense based on the underlying size of the types.</p>

<p><sup>2</sup>I lie a bit here. The performance presumably changed suddenly but I didn't actually notice until I again ran the benchmarks, after a series of other changes, leading to a painful bisection process where I determined the causal change. This is a great reason to have some type of performance-measuring continuous integration.</p>