通常而言,服务发布平台的构建部署的流程(镜像部署除外)会经过构建(同步代码->编译->打包->上传)、部署(下载包->解压到目标机器->重启服务)等步骤。以美团内部的发布平台Plus为例,最近我们发现一些发布项在构建产物打包压缩的过程中耗时比较久。如下图所示的pack步骤,一共消耗了1分23秒。
pack作为几乎所有需要部署的服务必需步骤,它目前的耗时基本上仅低于编译和构建镜像,因此,为了提高整体构建的效率,我们准备对pack打包压缩的步骤进行一轮优化工作。
发布项的包大小分析
为了尽可能地模拟构建部署中的应用场景,我们将2020年的部分构建包数据进行了整理分析,其中压缩后的包大小如下图所示,钟形曲线说明了整体的包体积呈正态分布,并且有着较明显的长尾效应。压缩后体积主要在200M以内,压缩前的大小大致在516.0MB以内。
而99%的服务压缩包大小会在1GB以内,而对于压缩步骤而言,其实越大的项目耗时越明显,优化的空间越大。因此,我们在针对性的方案对比测试中选择了1GB左右的构建包进行压缩测试,既能覆盖99%的场景,也可以看出压缩算法之间比较明显的提升。
这样选择的主要原因如下:
备注:由于在相同压缩库相同压缩比等配置的情况下,CompressionSpeed并没有明显变化,因此没有做其它包体积的批量测试和数据汇总。
本文中我们使用的测试项目为美团内部的较大型的C++项目,其中文件类型除去C++、Python、Shell代码文件,还有NLP、工具等二进制数据(不包括.git中存储的提交数据),数据类型比较全面。
目录大小为1.2G,也可以比较清晰地对比出不同方案之间的差距。
我们通常使用tar-czf命令来进行打包并且压缩的操作,z参数正是使用gzip的方式来进行压缩。DEFLATE标准(RFC1951)是一个被广泛使用的无损数据压缩标准。它的压缩数据格式由一系列块构成,对应输入数据的块,每一块通过LZ77(基于字典压缩,就是将最高概率出现的字母以最短的编码表示)算法和Huffman编码进行压缩,LZ77算法通过查找并替换重复的字符串来减小数据体积。
文件格式
我们可以看到gzip是主要基于CRC和HuffmanLZ77的DEFLATE算法,这也是后面ISA-L库的优化目标。
因为语言本身的特性,基于上下文的建模方法(ContextModeling)可以得到更好的压缩比,但由于它的性能问题很难普及。当前比较流行的突破算法有两种:
具体测试数据见下文。
对于小数据,它还特别提供一个载入预置词典的方法优化速度,词典可以通过对目标数据进行训练从而生成。
官方Benchmark数据对比
压缩级别可以通过–fast指定,提供更快的压缩和解压缩速度,相比级别1会导致压缩比率的一些损失,如上表所示。Zstd可以用压缩速度换取更强的压缩比。它是可配置的小增量,在所有设置下,解压缩速度都保持不变,这是大多数LZ压缩算法(如zlib或lzma)共享的特性。
LZ4是一种无损压缩算法,每核提供大于500MB/s的压缩速度(大于0.15Bytes/cycle)。它的特点是解码速度极快,每核速度为多GB/s(约1Bytes/cycle)。
从上面的Zstd的Benchmark对比中,我们看到了LZ4算法效果十分出众,因此我们也对LZ4进行了对比,LZ4更加侧重压缩解压速度,尤其是解压缩的速度,压缩比并不是它的强项,它默认支持1-9的压缩参数,我们分别进行了测试。
Pigz的作者MarkAdler,同时也是Info-ZIP的zip和unzip、GNU的gzip和zlib压缩库的共同作者,并且是PNG图像格式开发工作的参与者。
Pigz是gzip的并行实现的缩写,它主要思想就是利用多个处理器和核。它将输入分成128KB的块,每个块都被并行压缩。每个块的单个校验值也是并行计算的。它的实现直接使用了zlib和pthread库,比较易读,而且重要的是兼容gzip的格式。Pigz使用一个线程(主线程)进行解压缩,但可以创建另外三个线程进行读、写和检查计算,所以在某些情况下可以加速解压缩。
使用过SPDK(StoragePerformanceDevelopmentKit)或者DPDK(DataPlaneDevelopmentKit)应该也听说过ISA-L,前者使用了ISA-L的CRC部分,后者使用了它的压缩优化。ISA-L通过使用高效的SIMD(SingleInstruction,MultipleData)指令和专用指令,最大化的利用CPU的微架构来加速存储算法的计算过程。ISA-L底层函数都是使用手工汇编代码编写,并在很多细节上做了调优(现在经常要考虑ARM平台,本文中所提及的部分指令在该平台支持度不高甚至是不支持)。
ISA-L对压缩算法主要做了CRC、DEFLATE和Huffman编码的优化实现,官方的数据指出ISA-L相比zlib-1有5倍的速度提升。
举例来说,不少底层的存储开源软件实现的CRC都使用了查表法,代码中存储或者生成了一个CRCvalue的表格,然后计算过程中查询值,ISA-L的汇编代码中包含了无进位乘法指令PCLMULQDQ对两个64位数做无进位乘法,最大化intelPCLMULQDQ指令的吞吐量来优化CRC的性能。更好的情况是CPU支持AVX-512,就可以使用VPCLMULQDQ(PCLMULQDQ在EVEX编码的512bit版本实现)等其它优化指令集(查看是否支持的方式见“附录”)。
备注:截图来自crc32_ieee_by16_10.asm
使用
ISA-L实现的压缩优化级别支持[0,3],3为压缩比最大的版本,综合考虑我们采用了最大的压缩比,虽然压缩比2.346略低于gzip不过影响不大。在2019年6月发布的v2.27版本里,ISA-L加了多线程的Feature,因此在后续的测试中,我们采用了多线程并发的参数,效果提升比较显著。
由于我们构建机器的系统为Centos7需要自行编译ISA-L的依赖,比如nasm等库,所以在安装配置上会比较复杂,ISA-L支持多种优化参数比如,IGZIP_HIST_SIZE(压缩过程中加大滑动窗口),LONGER_HUFFTABLES,更大的Huffman编码表,这些编译参数也会对库有很大提升。
由于和gzip格式兼容,因此同样可以使用tar-xf命令进行解压,后续的解压缩测试过程中,我们使用的仍然是ISA-L提供的解压方式。
通过Pigz的测试,我们就在想,是否Zstd这样优秀的算法也可以支持并行呢,在官方的Repo中,我们十分惊喜地发现了一个“宝藏”。
Pzstd是C++11实现的并行版本的Zstandard(Zstd也在这之后加入了多线程的支持),类似于Pigz的工具。它提供了与Zstandard格式兼容的压缩和解压缩功能,可以利用多个CPU核心。它将输入分成相等大小的块,并将每个块独立压缩为Zstandard帧。然后将这些帧连接在一起以产生最终的压缩输出。Pzstd同样支持文件的并行解压缩。解压缩使用Zstandard压缩的文件时,PZstandard在一个线程中执行IO,而在另一个线程中进行解压缩。
下图是和Pigz的压缩和解压缩速度对比,来自官方Github仓库(机器配置为:IntelCorei7@3.1GHz,4threads),效果比Pigz还要出色,具体对比数据见下文:
备注:PiedPiper是美剧《硅谷》中虚拟出来的公司和算法。
本文中调研所提及的对比方案,都是统一在构建集群中的机器中进行测试,由于构建系统在线上的机器集群配置高度统一(包括硬件平台和系统版本),所以兼容性表现和测试数据也高度吻合。
实际上,部分官方的测试机器的配置和美团构建平台的物理机配置并不一致,场景可能也有区别,上面引用的测试结果中所使用的CPU以及平台架构和编译环境和我们所用的也有些出入,而且大多数还是家用的硬件比如i7-4790K或者i9-9900K,这也是需要使用构建平台的物理机和具体的构建包压缩场景来进行测试的原因,因为这样才能得出最接近我们使用场景的数据。
而在后面的方案实施中,由于部署需要稳定可靠的环境,所以我们暂时没有对部署机器做环境改造。
压缩比的对比
压缩比的对比中Zstd和Pzstd有一些优势,其中Brotli和LZ4由于支持的参数限制,比较难测试同级别压缩比下的速度,因此选择了压缩比稍低的参数,但是效率仍然距离Pigz和Pzstd存在一些差距。
而ISA-L的实现在压缩比上有一些牺牲,不过并没有差距很大。
优劣分析总结
比较于gzip,新算法都具有想当不错的速度,但是由于压缩格式的向前不兼容,加上需要客户端(部署目标机器)的支持,可能方案实施周期会较长。
而对于部署来说,可能收益并不是十分明显,反而加重了一些维护和运维成本,所以我们暂时没有把Pzstd的方案放到较高的优先级。
选型的策略主要有基于如下原则:
综合以上几点,决定一期采取ISA-L的方式加速,可以最稳定并且较高速地提升构建平台的效率,未来可能会实现Pzstd的方案,下面的数据为一期的结果。
而后我们将优化前的Pack步骤(压缩+上传)部分打点数据,以及优化后的部分打点数据做了汇总,得出了平均的优化效果对比,数据如下:
宏达,美团基础技术部研发工程师。
基础技术部-研发质量及效率部-代码仓库和构建组,团队旨在建设代码仓库管理、协作及代码构建能力,完善多维度的工作流执行引擎及构建工具链,与公司其他研发工具打通,提高业务整体的开发、交付效率。
机器环境
文中的测试统一在如下物理机中进行,测试中使用相同的目标文件。测试机使用的是非PCIESSD磁盘。