压缩能不能更快?
有一个比较常见的文件传输需求,数据库打快照,快照文件压缩打包,上传/下载, 解压,对端数据库加载
如何缩短这个链路的时间,数据库打快照的时间控制以及加载数据库的时间控制,不沟通用,这里不讨论
这里的快指的是快速嗷
打包/压缩
打包压缩有个典型的用法
#压缩
tar cf - __filexx__ | lz4 > __filexx__
tar cf - __filexx__ | snzip > __filexx__
#解压
lz4 -dc __filexx__ | tar -xv -C __dirxx__
snzip -dc __filexx__ | tar -xv -C __dirxx__
tar/lz4/snzip都有针对流的处理,unix这种设计非常的优雅,省拷贝
tar压缩是单线程,lz4的cli工具,用的也是单线程, snzip也是单线程
如何让这个压缩解压的流程更快?
针对多线程的lz4,我找到了一个实现
简单测试多线程版本的lz4 cli和原版lz4并没有速度上的差距?
解压是没法并行的
结合snzip,测了三个数据,确定是io到瓶颈了,三个数据相同
#6G数据,fasterkv生成
/usr/bin/time -p tar cf - hlog | ./lz4 > h #自己编的多线程lz4
real 42.96
user 0.10
sys 3.81
/usr/bin/time -p tar cf - hlog | lz4 > h
real 42.42
user 0.09
sys 3.31
/usr/bin/time -p tar cf - hlog | snzip > h
real 42.73
user 0.10
sys 3.46
查看iostat -x -y 1
能看到util %100 dstat反而看不出来
找了个ssd机器,重新跑, 100g数据
# cpu 120%, 磁盘没跑满
# 100G压缩到4G
/usr/bin/time -p tar cf - hlog | ./lz4 > h
real 98.81
user 1.66
sys 68.08
# cpu 80%, 磁盘也没跑满
/usr/bin/time -p tar cf - hlog | lz4 > h
real 126.30
user 1.79
sys 63.79
# cpu 100%, 磁盘也没跑满
# 100G压缩到13G
/usr/bin/time -p tar cf - hlog | ./snzip > h
real 105.23
user 2.15
sys 55.77
# 100G压缩到2.3G 多线程 gzip速度非常慢,放弃
/usr/bin/time -p tar cf - hlog | pigz > h
real 112.64
user 1.89
sys 108.99
解压速度
/usr/bin/time -p lz4 -dc h | tar -xv -C temp
real 141.59
user 57.58
sys 36.63
/usr/bin/time -p ./snzip -dc h | tar -xv -C temp
real 94.30
user 54.38
sys 29.29
/usr/bin/time -p pigz -dc h | tar -xv -C temp
real 161.14
user 209.26
sys 65.88
snappy解压要比lz4快的
gzip过于离谱,不测了
100G压缩/解压 snappy比lz4 快20 + 40 60 秒
但文件生成 100G-4G 100-13G
这个压缩比过于离谱,重新找一个数据来测
源数据 69G lz4 压缩到29G
#压缩
# lz4 69G - 29G
/usr/bin/time -p tar cf - hlog | ./lz4 > h
real 97.49
user 1.14
sys 50.86
/usr/bin/time -p tar cf - hlog | lz4 > h
real 165.71
user 1.11
sys 45.75
# 压缩
# snappy 69G - 31G
/usr/bin/time -p tar cf - hlog | ./snzip > h
real 124.31
user 1.39
sys 38.66
# 解压
/usr/bin/time -p lz4 -dc h | tar -xv -C temp
real 123.29
user 51.14
sys 34.83
/usr/bin/time -p ./lz4 -dc h | tar -xv -C temp
real 113.17
user 38.79
sys 35.28
/usr/bin/time -p ./snzip -dc h | tar -xv -C temp
real 77.99
user 40.26
sys 25.58
上传/下载
上传下载到时间需要确定
假设上传速度U m/s 下载速度D m/s
压缩比 Z
snappy压缩比 sn
lz4压缩比lz
文件总体积 T
Z(T/U + T/D) 带入上面的数据,上传下载假定30M 1G需要用 34s
snappy用时 13* 1024 / 30 *2 = 886s
lz4 用时间 4 * 1024 / 30 * 2 = 272s
考虑到压缩解压速度 snappy快60,并没有什么用
前面snappy节省的时间比在1以内,除非压缩比的压缩比在1以内,否则没有胜算
从上传下载速度考虑,除非差距在60以内,也就是说速度要在250M,才有优势
不同类型的文件有不同的压缩比,如果以当前的压缩比来考虑,snappy无胜算
第二组数据,其中压缩大小 69G - 29G(lz4) - 31G(snappy)
机器是32核
压缩算法\时间(s) | 压缩时间 | 解压时间 | 总时间 |
---|---|---|---|
lz4 | 165 | 124 | 289 |
lz4 multi-thread | 98 | 114 | 212 |
snappy | 124 | 78 | 202 |
压缩比的压缩比差距不大,体积差2G也就是68s,但是snappy的压缩解压并没有快68s,只快了十多秒
注意 lz4 multi-thread用的是std::thread::hardware_concurrency()
决定线程数, 而在docker中,这个返回的是宿主机的核数
可以用 stress -c 4确认 –cpus=2能限定cpu使用200%,但是对于多线程的个数无能为力。
补充三组,分别是4核 8核,16核 64核
压缩算法\时间(s) | 压缩时间 | 解压时间 | 总时间 |
---|---|---|---|
Lz4 2线程 | 95.59 | 113.84 | 210 |
lz4 4线程 | 99.84 | 118.45 | 219 |
lz4 8线程 | 95.47 | 114.88 | 211 |
lz4 16线程 | 97.11 | 114.90 | 213 |
lz4 64线程 | 98.80 | 113.35 | 213 |
可以看到 ,并没有随着线程数增加而扩展性能
lz4多线程版的实现逻辑是一个队列,worker去干活压缩,然后队列中的数据排队写到文件
压缩最终是写成一个文件,多线程并没有加速写文件的过程,只是加速了分片压缩,最终压缩好的数据还是要逐个排队写到文件里
这个场景实际上有点类似于logger多消费者单生产者模型(MPSC),logger肯定有多线程写入同步问题,如何快速的多线程写文件。我的观察,这个代码是有修改空间的。这里挖一个坑,考虑一下如何把nanolog/spdlog的逻辑搬过来
结论
上传速度一般,要好的压缩率,选lz4 多线程版
上传速度非常快,压缩率在速度面前无关紧要,选snappy,解压速度快
多线程版lz4和snappy压缩速度差不多,解压速度snappy更快(压缩率低)
多线程版本lz4还是有改进空间的比如去掉锁竞争之类的
其他
看了篇文章 How to speed up the unloading of LZ4 in ClickHouse
结论部分介绍了解压速度提升的一些总结,在memcpy上能有点改进,不过不能在lz4库上改,而是自己实现一套RLE压缩算法。另外收益有,但是不是决定性的
一个查询,耗时不仅仅在解压上,收益界定类似本文
有一些特别久的查询,要大量的获取冷数据,那么冷数据的压缩率高更省IO(读),所以选用zstd而不是lz4
也有没有用mmap /o_direct之类的零拷贝技术,也没有用一些提高checksum计算速度的技巧,压缩率高这些优化收益不大(why?)
参考
- 思路启发 https://www.codeproject.com/Questions/3689965/Multiple-threads-writing-to-a-single-file-in-C
- https://stackoverflow.com/questions/7565034/can-multiple-threads-write-into-a-file-simultaneously-if-all-the-threads-are-wr 这里也讨论了实际上fd有状态不太可能多线程都持有fd来写,需要同步。实际上还是退化成乱序操作之后顺序写这个代价要最小化
- 也考虑过把tar内嵌到应用里,这里有个简单实现 https://github.com/rxi/microtar