3

让压缩库ZSTD在ARM上更顺滑

 1 year ago
source link: https://yikun.github.io/2020/05/20/%E8%AE%A9%E5%8E%8B%E7%BC%A9%E5%BA%93ZSTD%E5%9C%A8ARM%E4%B8%8A%E6%9B%B4%E9%A1%BA%E6%BB%91/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Facebook的ZSTD压缩库从1.0版本发布的那天起,就引起了业界的关注,对比业界常用的压缩库lz4、zilib、xz,ZSTD更注重速度和压缩比的均衡,对比zlib来看,更是在保证压缩比的情况下,较zlib压缩性能提升6倍左右,解压性能提升2倍左右。

我们团队也在2020年年初时,对ZSTD压缩库进行了性能优化,最终优化已推入到Facebook的上游社区中,本文将详细的介绍我们进行的优化。

1. 利用neon指令集对数据复制优化。

完整的Patch链接:facebook/zstd#2041

优化思路:

aarch64提供了一系列的neon指令,本次优化则利用了VLD和VST指令,借助neon寄存器进行读写加速,ARM的官方文档是这样描述这两个指令的:

VLDn and VSTn (single n-element structure to one lane)

  • Vector Load single n-element structure to one lane. It loads one n-element structure from memory into one or more NEON registers. Elements of the register that are not loaded are unaltered.
  • Vector Store single n-element structure to one lane. It stores one n-element structure into memory from one or more NEON registers.

来自ARM的官方文档Coding for Neon - Part 1: Load and Stores中,写的非常详细,引用一张图来描述neon寄存器和memory加载和存储的方式,核心思想就是:利用neon寄存器作为暂存的中转站,加速数据处理
image

我们以u8的复制为例,总结下本次我们在ZSTD具体的优化实现:

static void ZSTD_copy8(void* dst, const void* src) {
#ifdef __aarch64__
vst1_u8((uint8_t*)dst, vld1_u8((const uint8_t*)src));
#else
memcpy(dst, src, 8);
#endif
}

核心步骤包含两步:

  1. 将src利用vld1加载到neon寄存器。
  2. 使用vst1将neon寄存器的值store到dst的memory中。

这样便利用neon完成了对u8的memcpy的优化,对于此类优化,有兴趣的可以阅读What is the fastest way to copy memory on a Cortex-A8?,了解在Cortext-A8的架构下,如何快速的进行memory copy。

性能测试:

完成neon优化后,我们对压缩和解压缩都进行了测试,最终,在压缩场景获得了大概1+%的提升:

Average gains(level 1~19) gcc9.2.0 clang9.0.0
Compression 1.67% 1.23%
Decompression 0.02% 0.36%

2. 使用prefetch机制加速数据读取。

完整的Patch链接:facebook/zstd#2040

优化思路:

Prefetch的中文是预取,原理是通过将数据预取到cache中,加速数据的访问。一个比较常见的场景就是在循环中,我们可以通过显示的调用,充分的预取未来将会访问的数据或指令便能快速从Cache中加载到处理器内部进行运算或者执行。

在Jeff Dean的一次经典的talk–Software Engineering Advice from
Building Large-Scale Distributed Systems
中,提到了cache和memory的速度差异,大致如下图所示:
image

可以看到,从cache中拿数据,将比直接从memory拿数据性能提升几十甚至上百倍,因此,我们也在本次的优化中,为aarch64加入的预取指令

#define PREFETCH_L1(ptr)  __asm__ __volatile__("prfm pldl1keep, %0" ::"Q"(*(ptr)))
#define PREFETCH_L2(ptr) __asm__ __volatile__("prfm pldl2keep, %0" ::"Q"(*(ptr)))

同时,将预取加速加入到了ZSTD_compressBlock_fast_generic和ZSTD_compressBlock_doubleFast_generic的主循环中,在数据访问前,预先先将数据加载到cache中,从而加速后续访问对数据读取。

性能测试:

我们仅对压缩进行了优化,因此,也仅对压缩进行了测试,测试结果可以看出,速度在aarch64架构下获得了1.5-3+%的提升:

Average gains(level 1~19) gcc9.2.0 clang9.0.0
level 1~2 3.10% 3.69%
level 3~4 2.49% 1.51%

在Facebook的ZSTD中,我们使用了neon指令集对memcpy的过程进行了加速,同时,也利用了prefetch机制,加速了循环时数据的访问。

希望本篇文章,能够对大家带来一些性能优化的启发。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK