最快,最有效的方法来获取gzip压缩文件中的记录(行)数


16

我正在尝试对7.6 GB gzip文件进行记录计数。我发现使用该zcat命令的方法很少。

$ zcat T.csv.gz | wc -l
423668947

这可以工作,但是需要太多时间(超过10分钟才能获得计数)。我尝试了更多类似的方法

$ sed -n '$=' T.csv.gz
28173811
$ perl -lne 'END { print $. }' < T.csv.gz
28173811
$ awk 'END {print NR}' T.csv.gz
28173811

这三个命令的执行速度都非常快,但计数错误28173811。

如何在最短时间内执行记录计数?


5
为什么需要计算记录数?如果要在处理它们之前先对它们进行计数,则意味着必须将文件解压缩两次。
安德鲁·亨利

3
有关执行此操作的详细信息将很有帮助。如果正在进行中-也就是说,您定期压缩一堆文件,而在以后需要知道记录的数量-为什么不将它们压缩后再计算,然后将其嵌入文件名中?
jamesqf

3
从机械磁盘读取9.7GB文件本质上较慢。将文件存储在SSD上,查看gunzip / zcat的运行速度。但是正如@jamesqf所说,将行数存储在文件名或tgz中的文件中,提取该文件会更快。
ChuckCottrill

2
有很多理论上的原因使您无法避免这项工作。从定义上说,一种压缩格式可让您确定数据的某些有用属性,而无需进行解压缩,这实际上不如可能是一种好的压缩格式:)
hobbs

Answers:


28

sedperlawk你提到的命令可能是正确的,但他们都读压缩的数据和计数换行符。这些换行符与未压缩数据中的换行符无关。

要计算未压缩数据中的行数,无法解压缩。您采用的方法zcat是正确的方法,并且由于数据非常大,因此解压缩花费一些时间。

大多数处理gzip压缩和解压缩的实用程序很可能会使用相同的共享库例程来执行此操作。加快速度的唯一方法是找到zlib比默认程序快某种方式的例程的实现,然后重新构建zcat以使用这些例程。


11
这将是不平凡的编程工作,但可行。关键是不要重建zcat。工作的重要部分zcat是生成实际输出。但是,如果仅计算\n字符,则没有必要。gzip压缩本质上是通过用较短的字符串替换常见的长字符串来工作的。因此,您只需要关心字典中包含的长字符串\n,并计算其中的(加权)出现次数即可。例如,由于英语规则,.\n是常见的16位字符串。
MSalters

19

使用unpigz。

Kusalananda的答案是正确的,您需要解压缩整个文件以扫描其内容。/bin/gunzip在单个内核上以最快的速度完成此操作。Pigzgzip可以使用多个内核的并行实现。

可悲的是,正常的gzip文件解压缩本身不能并行,但pigz确实提供了一个改进版本gunzipunpigz即做相关的工作,如阅读,写作,并在一个单独的线程执行校验。在某些快速基准测试中,unpigz速度几乎是gunzip我的核心i5机器的两倍。

安装pigz用你喜欢的包管理器,并使用unpigz替代gunzipunpigz -c代替zcat。因此,您的命令将变为:

$ unpigz -c T.csv.gz | wc -l

当然,所有这些都假定瓶颈是CPU,而不是磁盘。


4
我的pigz手册页指出,解压缩无法并行化,至少没有没有为此目的专门准备的放气流。结果,pigz使用单个线程(主线程)进行解压缩,但将创建其他三个线程进行读取,写入和检查计算,这在某些情况下可以加快解压缩速度。尽管如此,像你我觉得它至少两倍,比速度gzip,并行如果不是因为
斯特凡Chazelas

@StéphaneChazelas好点!这就解释了减压方面令人失望的加速。我编辑了帖子以更好地反映此信息。
marcelm '17年

5

所有管道的问题在于,您实际上要加倍工作。无论解压缩有多快,数据仍然需要传递到另一个进程。

Perl具有PerlIO :: gzip,可让您直接读取压缩的流。因此,即使其解压缩速度可能不符合以下条件,它也可能具有优势unpigz

#!/usr/bin/env perl

use strict;
use warnings;

use autouse Carp => 'croak';
use PerlIO::gzip;

@ARGV or croak "Need filename\n";

open my $in, '<:gzip', $ARGV[0]
    or croak "Failed to open '$ARGV[0]': $!";

1 while <$in>;

print "$.\n";

close $in or croak "Failed to close '$ARGV[0]': $!";

我在具有16 GB RAM的旧2010 MacBook Pro具有8 GB RAM的旧ThinkPad T400上使用了13 MB gzip压缩文件(解压缩为1.4 GB)进行了尝试,该文件已在缓存中。在Mac上,Perl脚本比使用管道要快得多(5秒vs 22秒),但是在ArchLinux上,它输给了unpigz:

$ time -p ./gzlc.pl spy.gz 
1154737
真正的4.49
用户4.47
sys 0.01

$ time -p unpigz -c spy.gz | wc -l
1154737
真实的3.68
用户4.10
sys 1.46

$ time -p zcat spy.gz | wc -l
1154737
真正的6.41
用户6.08
sys 0.86

显然,unpigz -c file.gz | wc -l无论是速度还是速度,赢家都是赢家。而且,无论多么简短,该简单的命令行肯定比编写程序要好。


1
与解压缩计算相比,我认为您大大高估了在两个进程之间移动数据所需的资源。尝试对各种方法进行基准测试;)
marcelm '17

2
@SinanÜnür在我的x86_64 Linux系统(也是旧硬件)gzip | wc上,其运行速度与perl脚本相同。并且pigz | wc是两倍快。gzip无论我将输出写入/ dev / null还是将管道写入wc我认为的是perl使用的“ gzip库”比gzip命令行工具都快的速度,都可以以相同的速度运行。也许管道还有另一个Mac / Darwin特有的问题。这个perl版本完全具有竞争力,这仍然令人惊讶。
rudimeier

1
在我的x86_64 Linux安装上,它似乎比更好,zcat也更坏unpigz。我惊讶于Linux系统上的管道比Mac上的管道快得多。我没有想到,尽管我曾经观察到,同一台Mac上同一CPU受限的Linux VM上运行同一程序的速度要比裸机上快。
锡南·努尔

1
那很有意思; 在我的系统(Debian 8.8 amd64,四核i5)上,perl脚本稍微慢一些 ……109M .gz文件解压缩为1.1G文本,持续需要5.4秒的时间zcat | wc -l,而需要花费5.5秒的perl脚本。老实说,我为人们在这里报道的变化感到惊讶,特别是在Linux和MacOS X之间!
marcelm

我不知道我是否可以概括我在Mac上看到的内容,这是奇怪的事情。使用解压缩后的1.4 GB文件wc -l需要2.5秒。gzcat compressed.gz > /dev/null需要2.7秒。然而,管道需要22秒。如果我尝试使用GNU wc,则在解压缩的文件上仅花费半秒,但在管道中需要22秒。GNU的zcat执行时间是原来的两倍zcat compressed.gz > /dev/null。这是在Mavericks,旧的Core 2 Duo CPU,16 GB RAM,Crucial MX100 SSD上。
锡南·努尔

4

库萨兰南达的答案基本上是正确的。要计算行数,您需要搜索换行符。但是,从理论上讲,无需完全解压缩文件即可搜索换行符。

gzip使用DEFLATE压缩。DEFLATE是LZ77和Huffman编码的组合。也许有一种方法可以只找出霍夫曼符号节点的换行符,而忽略其余部分。几乎可以肯定,有一种方法可以查找使用L277编码的换行符,保留字节数并忽略其他所有内容。

因此,恕我直言,在理论上可能比unpigz或zgrep更有效地提出解决方案。话虽这么说肯定不切实际(除非有人已经这样做了)。


7
这个想法的主要问题是,DEFLATE使用的霍夫曼符号与LZ77压缩后的序列相对应,因此它们与未压缩文件中的U + 000A字符之间可能没有简单的关系。例如,一个霍夫曼符号可能意味着“”的最后五位。后跟“ \ n”的前三位,另一个符号表示“ \ n”的后五位,后跟“ T”的所有八位。
zwol

@zwol不,Deflate算法的LZ77部分压缩字节序列,而不是比特序列。en.wikipedia.org/wiki/DEFLATE#Duplicate_string_elimination
罗斯·里奇

1
@RossRidge Huh,我不知道,但是我不认为这会使我的言论无效。在我看来,霍夫曼符号可以根据该参考的下一段看到,每个符号可以扩展为可变数量的位,它们不必产生整数个字节。
zwol

1
@zwol当然,您必须在比特流中搜索匹配的霍夫曼代码比特序列,但该答案没有其他建议。这个答案的问题在于确定哪个霍夫曼代码最终生成或更多个换行符并不容易。随着滑动窗口的移动,生成换行符的LZ77代码不断变化,这意味着霍夫曼代码也在变化。由于您只对换行感兴趣,因此您必须实现除输出部分以外的整个解压缩算法,以及滑动窗口的某些部分。
Ross Ridge)

1

可以使用zgrepwith -c标志和$parameter 来完成 。

在这种情况下,-c指示命令输出匹配的行数,而regex $匹配行尾,因此它匹配每一行或文件。

zgrep -c $ T.csv.gz 

正如@StéphaneChazelas评论说- zgrep只是一个脚本周围zcatgrep它应该到原来的建议提供相似的性能zcat | wc -l


2
嗨Yaron谢谢您的回答,即使zgrep花费的时间与zcat一样多,我需要找到我认为的其他方法
Rahul

8
zgrep通常是一个脚本zcat(调用gzip -dcq)来解压缩数据并将其提供给grep,所以不会有帮助。
斯特凡Chazelas

1
@StéphaneChazelas-感谢您的评论,请更新我的答案以反映它。
亚龙

0

如您所见,大多数答案都试图优化其功能:上下文切换的数量和进程间IO。原因是,这是您可以在此处轻松优化的唯一方法。

现在的问题是,其资源需求与减压的资源需求几乎可以忽略不计。这就是为什么优化不会真正使任何事情更快的原因。

在可以真正加速的地方,将使用修改后的un-gzip(即解压缩)算法,从而省去了实际的解压缩数据流。而它只计算在新行的数目解压缩的流从所述压缩的一个。很难,这需要gzip的算法(LZWHuffman压缩算法的某种组合)的深入了解。该算法很可能无法通过闪电来显着优化减压时间,而我们只需要知道换行数即可。即使有可能,本质上应该已经开发了一个新的gzip解压缩库(直到知道它才存在)。

对您问题的现实答案是,不,您不能使其明显更快。

也许您可以使用一些并行化的gzip解压缩(如果存在)。它可以使用多个CPU内核进行解压缩。如果不存在,则可以相对容易地进行开发。

对于xz,存在一个并行压缩器(pxz)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.