在JVM中加入CRC32 Intrinsic加速CRC校验
问题:
在Java应用中例如在Hadoop中,经常用到CRC校验计算。对于Java开发者来说,目前可用的实现是java.util.zip类,他通过JNI调用zlib中crc32函数。这里有两个性能瓶颈。其一,JNI的调用性能很低,如果每次计算的数据量很小,比如1、2个byte,那么JNI的调用开销远远高于计算开销。其二,虽然zlib的crc32实现是查表实现,但是本身性能还有很大的提升空间。
对此,Hadoop社区目前实现了Java版本的CRC32类来克服JNI的开销,它在JIT后速度会快于通过JNI调用的Native实现。下图是通过运行PureJava CRC32作者提供的benchmark程序得出的性能数据。
横坐标是每次计算的字节大小。可见,无论计算字节大小,PureJava实现明显快于Zlib CRC实现。
淘宝的优化方法:
对于瓶颈1,虽然PureJava实现比原来快了许多,但是这远远没有达到极致。为了避免JNI调用开销,可以通过实现JVM Intrinsic来优化。Intrinsic可以在JIT过程中将相应的实现inline到JIT后的生成的目标平台(如x86)指令中,例如Math.sin。
对于瓶颈2,SSE4.2指令集提供了CRC32指令加速校验计算,这个指令的latency为3,thoughput是1,相当给力。虽然多项式是crc32c和zlib的crc32不同,但是如果没有历史数据的包袱,还是可以应用的。
优化结果:
应用了CRC32指令并实现为JVM Intrinsic后,我们看一下这个实现将有什么样的表现?
是的,您没有看错,运行同样的benchmark程序,应用CRC32指令的JVM Intrinsic实现了恐怖的完胜。在1个字节计算下,zlib JNI实现速度是13M/s,PureJava实现是93M/s,CRC32 Intrinsic是114M/s。在1K字节计算下,CRC32 Intrinsic实现是PureJava实现速度的10倍,是Zlib JNI实现的20倍,达到了6GB/s。再次强调,这都是Java程序调用的性能。
结束了?
木有,木有。这还不是极致,第一,目前的Intrinsic实现为了方便,并没有将CRC32指令直接inline到JIT后的指令中,需要通过call指令调用。第二,注意到CRC32指令的latency是3,目前实现没有利用ILP,理论上还能提高3倍性能。实际上我们已经有了再提高2.6倍的实现。是的,再提高2.6倍。
预知后事如何,敬请持续关注核心系统研发部专用计算组。