Java 为什么Arrays.equals(char[],char[])比所有其他版本快8倍? 短篇小说
根据我对几个不同Oracle和OpenJDK实现的测试,它似乎比其他类型的所有其他变体快8倍 如果应用程序的性能与比较数组是否相等密切相关,这意味着您非常希望将所有数据强制转换为Java 为什么Arrays.equals(char[],char[])比所有其他版本快8倍? 短篇小说,java,arrays,performance,optimization,Java,Arrays,Performance,Optimization,根据我对几个不同Oracle和OpenJDK实现的测试,它似乎比其他类型的所有其他变体快8倍 如果应用程序的性能与比较数组是否相等密切相关,这意味着您非常希望将所有数据强制转换为char[],以获得这一神奇的性能提升 长话短说 最近,我正在编写一些高性能代码,用于比较用于索引到结构中的键。键可能相当长,并且通常只在后面的字节中不同,因此此方法的性能非常重要 有一次,我使用了char[]类型的键,但作为推广服务的一部分,为了避免从byte[]和ByteBuffer的底层源中复制一些内容,我将其改
char[]
,以获得这一神奇的性能提升
长话短说
最近,我正在编写一些高性能代码,用于比较用于索引到结构中的键。键可能相当长,并且通常只在后面的字节中不同,因此此方法的性能非常重要
有一次,我使用了char[]
类型的键,但作为推广服务的一部分,为了避免从byte[]
和ByteBuffer
的底层源中复制一些内容,我将其改为byte[]
。突然,许多基本操作的性能下降了约3倍。我追溯到上面提到的事实:Arrays.equals(char[],char[])
似乎比所有其他Arrays.equals()
版本享有特殊地位,包括语义相同的short[]
(并且可以使用相同的底层代码实现,因为签名不会影响equals的行为)
因此,我编写了一个测试数组的所有基本变量。equals(…)
1和char[]
变量压碎所有其他变量,如上所示
现在,~8x阵列的这种优势并没有以同样的规模扩展到更小或更大的阵列,但它仍然更快
对于小型阵列,常数因素似乎开始占主导地位,而对于大型阵列,L2/L3或主内存带宽开始发挥作用(在前面的图中,您已经可以非常清楚地看到后一种效果,int[]
尤其是long[]
阵列在大型阵列中的性能开始下降)。下面是相同测试的一个示例,但使用较小的小阵列和较大的大阵列:
在这里,char[]
仍然很重要,只是没有以前那么多。小数组(只有16个元素)的每元素时间大约是标准时间的两倍,这可能是由于函数开销:大约0.5 ns/元素时,char[]
variant仍然只需要整个调用大约7.2纳秒,或者在我的机器上大约19个周期,因此少量的方法开销会大大减少运行时的开销(而且,基准测试开销本身就是几个周期)
在大端,缓存和/或内存带宽是一个驱动因素。long[]
变体的时间几乎是int[]
变体的2倍。short[]
变体,尤其是byte[]
变体的效率不是很高(它们的工作集仍然适合我机器的L3)
char[]
与其他所有类型的应用程序之间的差异非常大,对于依赖数组比较的应用程序来说(对于某些特定的域来说,这其实并不罕见),值得尝试将所有数据放入char[]
中加以利用
给出了什么?是因为char
是一些String
方法的基础,所以得到了特殊的处理吗?这只是JVM优化方法在基准测试中受到重创的另一种情况,而不是将相同的(明显的)优化扩展到其他基本类型(特别是short
,在这里是相同的)
0……甚至不是那么疯狂——考虑各种系统,例如依赖于(冗长)哈希比较来检查值是否相等,或者哈希图,其中键长或可变大小。 1我没有在结果中包括
boolean[]
、float[]
和double[]
或double,以避免图表混乱,但对于记录boolean[]
和float[]
执行与int[]
相同的操作,而double[]
执行与long[]相同的操作
。根据类型的基本大小,这是有意义的
2我在这里说了一点。性能可能突然发生了变化,但我实际上没有注意到,直到我再次运行基准测试,在一系列其他变化之后,导致了痛苦的对分过程,我确定了因果变化。这是一个很好的理由来进行某种类型的性能测量连续集成。因为对于ars、SSE3和4.1/4.2都非常擅长检查状态变化。JVM生成的字符操作代码更加优化,因为这正是Java在web应用程序中经常使用的。Java在优化其他类型的数据方面非常糟糕。这正是beast的本质 在Scala和GoSu中也可以观察到同样的行为。现在传输的大部分信息都是文本形式的,因此除非您修改JVM,否则它会针对文本进行调整。而且,正如Marco所提到的,它下面是一个固有的C函数,这意味着它直接映射到高性能矢量化指令,如SSE4.x,甚至AVX2,如果标准JVM已经改进了很多
说真的,SSE4.x没有将字符和字节视为等效的数据类型,这就是文本分析更快的原因。此外,对于8位整数,在AVX2之前,指令并不存在。当我建议这是答案时,我可能会大发雷霆,但根据
数组#等于(char[],char[])
方法作为一个内在函数实现
最有可能是因为它在所有字符串比较中都非常关键。@Marco13 guess是正确的。HotSpot JVM对
数组.equals(char[],char[])
有(即特殊的手工编码实现),但对其他数组.equals方法没有
@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);
}
}
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
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