为什么尽管 CPU 和硬盘性能没有达到最大限度,但 gzip 仍然很慢?

为什么尽管 CPU 和硬盘性能没有达到最大限度,但 gzip 仍然很慢?

我有一些 JSON 文件,每个 20 GB,我想用以下命令压缩它们gzip

gzip file1.json

这占用了一个完整的 CPU 核心,一切正常。

它处理速度约为 25 MB/s(已在 中检查atop),我的硬盘读取速度为 125 MB/s,我有 3 个空闲的处理器核心,因此我希望在并行压缩多个文件时能够加快速度。因此我在其他终端中运行:

gzip file2.json
gzip file3.json
gzip file4.json

令人惊讶的是,我的吞吐量并没有增加;每个核心的 CPU 大约为 25%,而我的 HD 仍然只能以 25 MB/s 的速度读取。

为什么以及怎样解决?

答案1

我发现了:

原因是gzip(就当今的 CPU 速度与 HD 寻道速度而言)极低的缓冲区大小

它从输入文件中读取几 KB,压缩它,然后刷新到输出文件。考虑到这需要硬盘寻道,每秒只能执行几个操作。

我的表现没能扩大的原因是因为已经有一个人gzip在疯狂地寻求。


我通过使用 unix 解决了这个问题buffer公用事业:

buffer -s 100000 -m 10000000 -p 100 < file1.json | gzip > file1.json.gz

通过在将大量输入发送到 gzip 之前对其进行缓冲,可以显著减少小搜索的数量。选项如下:

  • -s-m指定缓冲区的大小(I相信单位是 KB,但不确定)
  • -p 100确保缓冲区 100% 填满后,数据才会传递给 gzip

并行运行四个,我可以获得预期的 4 * 25 MB/s 吞吐量。


我仍然想知道为什么 gzip 不允许增加缓冲区大小——这样,如果在旋转磁盘上运行它就没用了。

编辑:我尝试了更多压缩程序的行为:

  • bzip2由于压缩能力更强/更耗 CPU,因此处理速度仅为 2 MB/s
  • lzop似乎允许更大的缓冲区:每核 70 MB/s,2 个核心可以最大化我的 HD 而不会过度寻道

答案2

在查看了 MIT 开放式课程 6.172:“软件系统性能工程”的前五讲内容后,我在一个相当大的测试文件上运行了 Linux 性能分析器“perf”。结果似乎显示流水线停滞,一条指令必须等待前一条指令的结果。

       │         while (lookahead != 0) {                                                                
       │             /* Insert the string window[strstart .. strstart+2] in the                          
       │              * dictionary, and set hash_head to the head of the hash chain:                     
       │              */                                                                                 
       │             INSERT_STRING(strstart, hash_head);                                                 
  2.07 │       movzbl 0x8096d82(%edx),%eax                                                               
  3.99 │       mov    %edx,%ebp                                                                          
       │       shl    $0x5,%ecx                                                                          
  0.03 │       and    $0x7fff,%ebp                                                                       
  1.94 │       xor    %ecx,%eax                                                                          
  1.43 │       and    $0x7fff,%eax                                                                       
  2.01 │       mov    %eax,0x805e588                                                                     
  2.40 │       add    $0x8000,%eax                                                                      
  0.88 │       movzwl 0x8062140(%eax,%eax,1),%ecx                                                        
 23.79 │       movzwl %cx,%edi                                                                           
       │             /* Find the longest match, discarding those <= prev_length.  

倒数第二条指令正在复制到%ecx,最后一条指令必须等待(暂停流水线)直到%cx寄存器中有可用的数据。此流水线暂停会阻止包含循环。

这是一些非常模糊的“老派” C 编程风格的结果。

答案3

一个可能使其在多核/超线程 CPU 上的速度再上一个台阶的技巧:
(假设是 Ubuntu)

sudo apt-get 安装 moreutils

moreutils 包含“gnu parallel”等 - 它有很多选项可以帮助您更多地利用 CPU。

相关内容