使用AVX512可以将MD5的总哈希加速到800%
介绍
尽管在考虑哈希函数时,MD5哈希不再是一个不错的选择,但它仍在各种应用程序中使用。因此,可以考虑对MD5哈希速度进行的任何性能改进。
由于SIMD处理(AVX2,尤其是AVX512)最近有所改进,我们提供了Go md5-simd程序包,该程序包可将MD5哈希在AVX2上的总集成速度提高400%,在AVX512上的最高800%。
通过在单个CPU内核上并行运行8个(AVX2)或16个(AVX512)独立的MD5和,可以提高性能。
重要的是要了解这md5-simd 不会加快单个MD5哈希总和的速度。相反,它允许 在同一CPU内核上并行计算多个独立的MD5和,从而更有效地利用计算资源。
性能
下表比较了crypto/md5AVX2和AVX512代码之间的性能:

与相比crypto/md5,AVX2版本的速度提高了4倍:
$ benchcmp crypto-md5.txt avx2.txt benchmark old MB/s new MB/s speedup BenchmarkParallel/32KB-4 2229.22 7370.50 3.31x BenchmarkParallel/64KB-4 2233.61 8248.46 3.69x BenchmarkParallel/128KB-4 2235.43 8660.74 3.87x BenchmarkParallel/256KB-4 2236.39 8863.87 3.96x BenchmarkParallel/512KB-4 2238.05 8985.39 4.01x BenchmarkParallel/1MB-4 2233.56 9042.62 4.05x BenchmarkParallel/2MB-4 2224.11 9014.46 4.05x BenchmarkParallel/4MB-4 2199.78 8993.61 4.09x BenchmarkParallel/8MB-4 2182.48 8748.22 4.01x
与相比crypto/md5,AVX512的速度提高了8倍:
$ benchcmp crypto-md5.txt avx512.txt benchmark old MB/s new MB/s speedup BenchmarkParallel/32KB-4 2229.22 11605.78 5.21x BenchmarkParallel/64KB-4 2233.61 14329.65 6.42x BenchmarkParallel/128KB-4 2235.43 16166.39 7.23x BenchmarkParallel/256KB-4 2236.39 15570.09 6.96x BenchmarkParallel/512KB-4 2238.05 16705.83 7.46x BenchmarkParallel/1MB-4 2233.56 16941.95 7.59x BenchmarkParallel/2MB-4 2224.11 17136.01 7.70x BenchmarkParallel/4MB-4 2199.78 17218.61 7.83x BenchmarkParallel/8MB-4 2182.48 17252.88 7.91x
设计
md5-simd 同时具有AVX2(8通道并行版本)和AVX512(16通道并行版本)算法,可通过以下函数定义来加快计算速度:
//go:noescape func block8(state *uint32, base uintptr, bufs *int32, cache *byte, n int) //go:noescape func block16(state *uint32, ptrs *int64, mask uint64, n int)
AVX2版本基于md5vec存储库,除了微小(外观)更改外,基本上没有变化。
AVX512版本是从AVX2版本衍生而来,但增加了一些进一步的优化和简化。
在上ZMM寄存器中缓存
AVX2版本传入一个cache8内存块(约0.5 KB),用于临时存储中间结果,在ROUND1此期间其间将一直使用ROUND2到to ROUND4。
由于AVX512的寄存器数量增加了一倍(32个ZMM寄存器与16个YMM寄存器相比),因此可以使用高16个ZMM寄存器将中间状态保留在CPU上。因此,无需将相应的内容cache16传入AVX512块功能。
使用64位指针直接加载
AVX2使用VPGATHERDD指令(用于YMM)使用(8个独立的)32位偏移量并行加载8个通道。由于无法控制传递给(Go)blockMd5函数的8个条带如何布置到内存中,因此无法为所有8个条带派生“基本”地址和相应的偏移量(全部在32位内) 。
因此,AVX2版本使用临时缓冲区从所有8个inut切片中收集要散列的字节切片,并将此缓冲区以及(固定的)32位偏移量传递到汇编代码中。
对于AVX512版本,不需要此临时缓冲区,因为AVX512代码使用一对VPGATHERQD指令直接取消引用64位指针(从已初始化为零的基址地址)。
请注意,需要两个加载(收集)指令,因为AVX512版本并行处理16通道,总共需要加载16倍64位= 1024位。一个简单的VALIGND,VPORD随后用于将下半部分和上半部分合并为一个ZMM寄存器(包含16个通道的32位DWORDS)。
遮罩支持
由于指针是直接从Go切片中传入的,因此我们需要防止NULL指针。为此,在AVX512汇编代码中传递了16位掩码,在VPGATHERQD指令期间使用该16位掩码来掩盖可能会导致段冲突的通道。
运作方式
为了使操作尽可能简单,有一个“服务器”可以协调各个哈希状态并在输入新数据时对其进行更新。可以将其可视化如下:

只要有可用数据,服务器将收集多达16个散列的数据并并行处理所有16个通道。这意味着,如果有16个哈希具有可用数据,则所有车道将被填充。但是,由于可能并非如此,因此服务器将填充较少的车道并进行一轮回合。车道也可以部分填充。

在这个(简化的)示例中,上面的4条车道已满,部分2条车道已满,最后2条车道为空。黑色区域将简单地从结果中屏蔽掉并被忽略(请注意,实际实现中每个服务器使用16条通道)。
开源的
源代码是在MIT许可下开源的,可在github中的md5-simd存储库中找到。试一下,我们欢迎您提供任何反馈。