为什么Arrays.equals(char[],char[])比所有其他版本快8倍?
短篇小说
根据我对一些不同的Oracle和OpenJDK实现的测试,似乎Arrays.equals(char[],char[])
在某种程度上比其他类型的所有其他变体快8倍。
如果应用程序的性能与比较数组的相等性高度相关为 0,则意味着您几乎希望将所有数据强制放入 ,只是为了获得这种神奇的性能提升。char[]
说来话长
最近,我正在编写一些高性能代码,这些代码使用Arrays.equals(...)
来比较用于索引到结构中的键。密钥可能很长,并且通常仅在后面的字节中有所不同,因此此方法的性能非常重要。
有一次,我使用了类型的键,但作为泛化服务的一部分,为了避免从和的底层源复制一些副本,我将其更改为。突然2,许多基本操作的性能下降了约3倍。我追溯了上面提到的事实:似乎比所有其他版本享有特殊的地位,包括语义上相同的版本(并且可以使用相同的底层代码实现,因为符号性不会影响equals的行为)。char[]
byte[]
ByteBuffer
byte[]
Arrays.equals(char[], char[])
Arrays.equals()
short[]
因此,我编写了一个 JMH 基准测试来测试 1 的所有原始变体,并且该变体会粉碎所有其他变体,如上所示。Arrays.equals(...)
char[]
现在,~8倍品种的这种主导地位并没有以相同的幅度扩展到更小或更大的阵列 - 但它仍然更快。
对于小型阵列,恒定因子似乎开始占主导地位,而对于较大的阵列,L2/L3 或主内存带宽开始发挥作用(在上图中,您已经可以非常清楚地看到后一种影响,其中阵列(尤其是阵列)在大尺寸下的性能开始下降)。下面看一下相同的测试,但使用较小的小数组和较大的大数组:int[]
long[]
在这里,还在踢屁股,只是没有以前那么多了。小数组(只有16个元素)的每元素时间大约是标准时间的两倍,可能是由于功能开销:在大约0.5 ns /元素时,变体在整个调用中仍然只需要大约7.2纳秒,或者在我的机器上大约需要19个周期 - 所以少量的方法开销会占用很多运行时(此外, 基准开销本身是几个周期)。char[]
char[]
在大端,缓存和/或内存带宽是一个驱动因素 - 变体的长度几乎是变体的2倍。特别是变体不是很有效(它们的工作集仍然适合我机器中的L3)。long[]
int[]
short[]
byte[]
与所有其他应用程序之间的差异非常大,以至于对于依赖于数组比较的应用程序(对于某些特定域来说,这实际上并不罕见),尝试将所有数据都放入其中以利用是值得的。哼。char[]
char[]
什么原因?是否因为某些方法的基础而受到特殊对待?这是否只是JVM优化方法的另一种情况,这些方法在基准测试中受到严重打击,并且没有将相同(明显)的优化扩展到其他基元类型(特别是这里相同的基元类型)?char
String
short
0 ...这甚至不是那么疯狂 - 考虑各种系统,例如,依靠(冗长的)哈希比较来检查值是否相等,或者哈希映射,其中键很长或大小可变。
1 个我没有在结果中包含 和/或 double,以避免图表混乱,但对于记录和执行相同,而执行相同。根据类型的基础大小,这是有道理的。boolean[]
float[]
double[]
boolean[]
float[]
int[]
double[]
long[]
阿拉伯数字我在这里撒谎了一点。性能可能突然发生了变化,但我实际上并没有注意到,直到我在一系列其他变化之后再次运行基准测试,导致一个痛苦的对等分过程,我确定了因果变化。这是进行某种类型的性能衡量持续集成的重要原因。