当 awk 太慢时,基于字段拆分大文件的最佳方法

use*_*102 5 large-files awk gzip text-processing split

我在处理巨大的 .gz 文件(大于 500G)时遇到问题。我的目标是通过这些文件中的第 4 个字段拆分这些文件中的每一个。我以前用过这个漂亮的 awk one-liner 来做到这一点:

zcat file.txt.gz | awk 'NR>1{print >  $4}'
Run Code Online (Sandbox Code Playgroud)

但不幸的是,这需要很长时间才能处理大文件,所以我试图首先按大小拆分它们,然后在按字段拆分后连接每个文件。我可以使用以下方法拆分它们:

i=file.txt.gz
dir=$i
mkdir -p $dir
cd $dir
split -b 200M ../$i $i

for file in `ls *`; do zcat $file | awk 'NR>1{print >  $4}'; done
Run Code Online (Sandbox Code Playgroud)

但是我如何通过第 4 个字段连接所有正确的文件?另外,真的没有更好的方法来做到这一点吗?当我使用这样的 gz 文件拆分时,我也遇到了一个错误,比如“文件意外结束”,所以我想我的拆分也是错误的,但我不确定我是否朝着正确的方向前进,如果你有建议会很有帮助。

非常感谢你的帮忙!弗拉

mr.*_*tic 2

Sat\xc5\x8d Katsura\ 的文件描述符注释是在正确的轨道上,假设有超过 1021 个(通常用户 FD 限制为 1024,stdin/stdout/stderr 为 -3)$4 的不同值,并且您正在使用gawk

\n\n

>当您使用或打印到文件时>>,文件将保持打开状态,直到出现显式close(),因此您的脚本正在累积 FD。自 Gawk v3.0 之前开始,FD 耗尽 ( ulimit -n) 的处理是透明的:遍历打开文件的链表,并且“暂时”关闭 LRU(最近最少使用)(从操作系统角度关闭以释放 FD) ,gawk在内部对其进行跟踪,以便稍后在需要时透明地重新打开)。您可以通过-W lint在调用时添加来看到这种情况的发生(从 v3.1 开始)。

\n\n

我们可以像这样模拟问题(在bash):

\n\n
printf "%s\\n" {0..999}\\ 2\\ 3\\ 0{0..9}{0..9}{0..9} | time gawk -f a.awk\n
Run Code Online (Sandbox Code Playgroud)\n\n

这会生成 1,000,000 行输出,其中包含 1000 个唯一值 4 美元,在我的笔记本电脑上大约需要 17 秒。我的限制是 1024 个 FD。

\n\n
 printf "%s\\n" {0..499}\\ 2\\ 3\\ {0..1}{0..9}{0..9}{0..9} | time gawk -f a.awk\n
Run Code Online (Sandbox Code Playgroud)\n\n

这也会生成 1,000,000 行输出,但由于有 2000 个唯一值 $4 \xe2\x80\x94,运行需要大约 110 秒(时间长了六倍多,并且有 1M 额外的次要页面错误)。

\n\n

从跟踪 $4 的角度来看,上面是“最悲观”的输入,输出文件每一行都会更改(并保证每次都需要(重新)打开所需的输出文件)。

\n\n

有两种方法可以帮助解决这个问题:减少文件名使用的混乱(即按 $4 进行预排序),或者使用 GNU 对输入进行分块split

\n\n

预分选:

\n\n
printf "%s\\n" {0..499}\\ 2\\ 3\\ {0..1}{0..9}{0..9}{0..9} | \n  sort -k 4 | time gawk -f a.awk\n
Run Code Online (Sandbox Code Playgroud)\n\n

(您可能需要调整sort选项以同意awk\ 的字段编号)

\n\n

在大约 4.0 秒时,这甚至比第一种情况更快,因为文件处理被最小化。$TMPDIR(请注意,对大文件进行排序可能会使用或中的磁盘临时文件/tmp。)

\n\n

split

\n\n
printf "%s\\n" {0..499}\\ 2\\ 3\\ {0..1}{0..9}{0..9}{0..9} | \n  time split -l 1000 --filter "gawk -f a.awk"\n
Run Code Online (Sandbox Code Playgroud)\n\n

这大约需要 38 秒(因此您可以得出结论,即使启动 1000 个进程的开销也gawk小于低效的内部 FD 处理)。在这种情况下,您必须在 awk 脚本中使用>>而不是,否则每个新进程都会破坏以前的输出。>(如果您重新调整代码来调用,同样的警告也适用close()。)

\n\n

您当然可以结合使用这两种方法:

\n\n
printf "%s\\n" {0..499}\\ 2\\ 3\\ {0..1}{0..9}{0..9}{0..9} | \n  time split -l 50000 --filter "sort -k 4 | gawk -f a.awk"\n
Run Code Online (Sandbox Code Playgroud)\n\n

这对我来说大约需要 4 秒,调整分块 (50000) 可以让您在进程/文件处理开销与sort磁盘使用要求之间进行权衡。YMMV。

\n\n

如果您提前知道输出文件的数量(并且不是太大),您可以使用 root 来增加(例如ulimit -n 8192,然后su自己),或者您也可以一般调整限制,请参阅如何我可以增加所有进程的打开文件限制吗?。该限制将由您的操作系统及其配置(如果您不幸的话,可能还有 libc)决定。

\n