为什么GNU Parallel分块会提高Gzip的压缩大小?

9

文件归类于:“意想不到的高效部门”。

前9000万个数字占用大约761MB,由以下输出:

 seq 90000000

根据 man parallel,它可以通过将输入分块并使用不同的 CPU 来压缩这些块来加速 gzip 压缩大文件的速度。因此,即使 gzip 是单线程的,这种技术使其变成了多线程:

seq 90000000  | parallel --pipe --recend '' -k gzip -9 >bigfile.gz

在一台Intel Core i3-2330M (4) @ 2.2GHz的电脑上,花费了46秒。

将其传输到普通的gzip

seq 90000000  | gzip -9 > bigfile2.gz

在同一台CPU上,只需要80秒。现在惊喜来了:

ls -log bigfile*.gz

输出:

-rw-rw-r-- 1 200016306 Jul  3 17:27 bigfile.gz
-rw-rw-r-- 1 200381681 Jul  3 17:30 bigfile2.gz

文件大小增加了300K?这看起来不对。首先,我用zdiff检查了文件是否具有相同的内容-是的,相同的。我本来以为任何压缩器都能比分块的数据流更好地处理连续的数据流。为什么bigfile2.gz没有比bigfile.gz更小呢?


有趣的是,在我的iMac上,使用并行和标准调用的bigfile2.gz文件大小几乎相同,经过的时间也几乎相同。 - Mark Setchell
1
@MarkSetchell 由于某种原因,Mac OS X的seq命令无法产生相同的输出。您可以尝试使用jot命令代替。 - Mark Adler
1
值得注意的是,“pigz” 比 “parallel+gzip” 更小更快(这里是198345773,而“gzip”是200381681;52秒用户和6½秒实际时间,相对于36½秒的用户和实际时间)。 - Toby Speight
"parallel --pipe" 不够高效。如果可能的话,请使用 "parallel --pipepart"(在这种情况下不行,因为您是从管道中读取,但如果您有一个文件,--pipepart 会更快)。 - Ole Tange
3个回答

9
原因是对于这种特殊而不寻常的输入,较小的deflate块比较大的块更好。默认情况下,gzip使用较大的deflate块,因为这对于普通输入数据效果最佳。 parallel命令通过每1 MB分割输入来强制使用一些较小的deflate块,从而获得小幅增益。尽管大多数块仍然具有相同的大小。
您可以通过在deflateInit2()中使用zlibmemLevel参数设置每个块的较小块大小来取得更好的效果。在此示例中,我每次使用单个线程压缩相同的输出,使用从9到2的memLevel值,其中较小的memLevel表示较小的deflate块大小(请注意,在默认级别上,zlib比您的gzip表现稍好)。
  • 9 - 199688429
  • 8 - 198554111 (默认值)
  • 7 - 191582070
  • 6 - 184880482
  • 5 - 181295029
  • 4 - 180137425 (此输入的最佳值)
  • 3 - 181176610
  • 2 - 185759115

对于这个数据来说,最佳的memLevel是4,压缩后的数据比默认的memLevel为8时少了12MB(9%)。对于memLevel为8,deflate块的大小为16383个符号,而对于memLevel为4,deflate块的大小为1023个符号。一个符号可以是一个字节或一个匹配。

改善是由于输入的极度规律性导致了一系列匹配和文字命令的规律序列。块大小越小,出现的这些不同命令就越少,从而需要更少的位来编码每个命令。这在memLevel 3时仍然适用,但此时每个deflate块开头的代码描述的开销抵消了减少不同代码的改进。

zopfli是一种优化块大小和选择的deflate压缩器,成功将其压缩为100,656,812字节。不过这需要三个半小时的时间!使用压缩等级11通过pigz调用zopfli

只是为了明确起见,zlibmemlevel 2-9 选项与 gzip 的压缩速度 -# (1-9) 选项不同,对吗? - agc
1
正确。1-9是压缩级别,它控制压缩器搜索匹配字符串的难度。实际上,对于这个输入,默认级别6的压缩效果比9更好!但这是另一个故事了。 - Mark Adler
如果使用较少的不同命令,您可以通过使用较小的块大小来提高效率。我想手动构建一个针对这些数据的deflate流,它将具有非常小的块,每个新序列的1000个数字只需要一个数字来引入,然后另一个块只包含其他999个数字的匹配项。请参阅我的zopfli注释,该注释进行了优化。稍后我会检查它使用了哪些块大小。 - Mark Adler
事实上,这非常有趣。可能可以压缩9 * 9次并查看哪个结果最好... - Shimon Doodkin
@ShimonDoodkin 加油。 - Mark Adler
显示剩余2条评论

0

我认为这是字典制作的频率不同造成的。 这就是速度和压缩效率之间的平衡,就像gziplzma一样。

我猜在分割情况下更频繁。 因此,字典的数量更类似于以下内容。

有一个20分钟的YouTube讲座,Raul Fraile: How GZIP compression works | JSConf EU 2014


回复:“以下内容”。不太清楚“以下内容”指的是什么名词对象。很抱歉,Raul Fraile的演讲以浓重的西班牙口音、腼腆柔和的单调语调被自称压缩领域非专家的他所发表,对于我这个习惯于快速说话者的美国人来说,听起来有些慢--最好只引用您认为相关的部分,或链接到视频中最相关的片段。 - agc

0

这种效果很可能是由于压缩块大小造成的。使用一系列设置对相同的输入流进行压缩,就像这样:

for i in {1..9}; do seq 90000000 | gzip -$i >$i.gz; done

给出文件大小,最小值为gzip -5

-rw-r--r-- 1 203473375 Jul  4 16:39 1.gz
-rw-r--r-- 1 201160853 Jul  4 16:40 2.gz
-rw-r--r-- 1 200181562 Jul  4 16:40 3.gz
-rw-r--r-- 1 204266147 Jul  4 16:40 4.gz
-rw-r--r-- 1 199144028 Jul  4 16:40 5.gz
-rw-r--r-- 1 199688429 Jul  4 16:40 6.gz
-rw-r--r-- 1 199689546 Jul  4 16:41 7.gz
-rw-r--r-- 1 200376213 Jul  4 16:41 8.gz
-rw-r--r-- 1 200381681 Jul  4 16:42 9.gz

这与gzip的默认值-6相差不远。


1
不,这里并没有产生这种效果。压缩级别并没有被改变。此外,压缩级别也不会改变块大小。你看到的是另一种效果,即更高的压缩级别找到了更长的匹配,但这种改进被更多不同长度和距离所抵消,需要更多的位来编码每个匹配。 - Mark Adler
我曾认为gzip程序在设置压缩级别时会更改块大小,但现在我被纠正了。感谢@Mark指出我的错误! - Toby Speight
趣闻:浪费了15分钟的CPU时间,制作了一个比较parallel和普通gzip表格的时间。运行命令为time for f in {1..9} ; do echo $f" " $(seq 90000000 | gzip -$f | wc -c) " " $(seq 90000000 | parallel --pipe --recend '' -k gzip -$f | wc -c) ; done,结果显示,在-1-3之间,普通gzip稍微小一些,之后变大。parallel在使用gzip -5时达到最小值,为198735045字节。 - agc
更多小知识:在那个循环中加上pigz$(seq 90000000 | pigz -$f | wc -c),显示它的最佳压缩级别也是-5,压缩后大小为197271587字节。pigz每次都是最小的,除了在-2的情况下,在这种情况下,pigz排名第二,紧随gzip之后。 - agc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接