【问题标题】:AWK taking very long to process fileAWK 处理文件需要很长时间
【发布时间】:2020-05-16 01:24:54
【问题描述】:

我有一个包含大约 600 万条记录的大文件。我需要根据前 17 个字符将此文件分块为较小的文件。因此前 17 个字符相同的记录将被分组到同名文件中

我使用的命令是:

awk -v  FIELDWIDTHS="17"  '{print > $1".txt"}' $file_name

问题是这非常缓慢。对于一个包含 800K 记录的文件,大约需要一个小时才能完成。

样本输入将是:-

AAAAAAAAAAAAAAAAAAAAAAAAAAAA75838458
AAAAAAAAAAAAAAAAAAAAAAAAAAAA48234283
BBBBBBBBBBBBBBBBBBBBBBBBBBBB34723643
AAAAAAAAAAAAAAAAAAAAAAAAAAAA64734987
BBBBBBBBBBBBBBBBBBBBBBBBBBBB18741274
CCCCCCCCCCCCCCCCCCCCCCCCCCCC38123922

这个问题有更快的解决方案吗?

我读到 perl 也可以用来分割文件,但我在 perl 中找不到像 fieldwidths 这样的选项..

任何帮助将不胜感激

uname:Linux

bash-4.1$ ulimit -n 1024

【问题讨论】:

  • 如果所有记录都有唯一的前缀,我认为任何改进都不会对性能产生相当大的影响
  • 有多少个唯一前缀?首先根据前缀对文件进行排序可能会更快(直观地相反),以便将每个前缀文件一次写入所有文件。您可能还会遇到基于文件数量的性能问题...
  • 你的脚本生成了多少文件?将ulimit -n 的输出和您的操作系统名称添加到您的问题中。
  • @dawg 我们预计大约有 5-6 条记录具有相同的前缀。所以一个 800K 的记录文件生成了大约 12000 个较小的文件
  • @Cyrus : 将 uname 和 ulimit 的输出添加到上述问题中。

标签: awk


【解决方案1】:
sort file |
awk '{out=substr($0,1,17)".txt"} out != prev{close(prev); prev=out} {print > out}'

包括性能改进:

  1. 通过不引用任何字段,它允许 awk 不进行字段拆分
  2. 通过首先排序并仅在输入的关键部分发生更改时更改输出文件名,它让 awk 一次只能使用 1 个输出文件,而不必管理打开/关闭可能数千个输出文件

而且它可以移植到所有 awk,因为它不使用像 FIELDWIDTHS 这样的 gawk 特定扩展。

如果每个输出文件中的行在排序后必须保留其原始相对顺序,那么它会是这样的(假设输入中没有空格,就像您提供的示例中一样):

awk '{print substr($0,1,17)".txt", NR, $0}' file |
sort -k1,1 -k2,2n |
awk '$1 != prev{close(prev); prev=$1} {print $3 > $1}'

在借用@dawg 的脚本(perl -le 'for (1..120000) {print map { (q(A)..q(Z))[rand(26)] } 1 .. 17} ' | awk '{for (i=1; i<6; i++) printf ("%s%05i\n", $0, i)}' | awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}'| sort -n | cut -c8- > /tmp/test/file - 谢谢!)生成他拥有的相同类型的示例输入文件后,以下是上述时间:

$ time sort ../file | awk '{out=substr($0,1,17)".txt"} out != prev{close(prev); prev=out} {print > out}'

real    0m45.709s
user    0m15.124s
sys     0m34.090s

$ time awk '{print substr($0,1,17)".txt", NR, $0}' ../file | sort -k1,1 -k2,2n | awk '$1 != prev{close(prev); prev=$1} {print $3 > $1}'

real    0m49.190s
user    0m11.170s
sys     0m34.046s

对于@dawg,用于比较在与上述相同的机器上运行相同的输入...我在它运行了 14 分钟以上后将其杀死:

$ time awk -v  FIELDWIDTHS="17"  '{of=$1 ".txt"; if (of in seen){ print >>of } else {print >of; seen[of]; } close(of);}' ../file

real    14m23.473s
user    0m7.328s
sys     1m0.296s

【讨论】:

    【解决方案2】:

    我创建了一个这种形式的测试文件:

    % head file
    SXXYTTLDCNKRTDIHE00004
    QAMKKMCOUHJFSGFFA00001
    XGHCCGLVASMIUMVHS00002
    MICMHWQSJOKDVGJEO00005
    AIDKSTWRVGNMQWCMQ00001
    OZQDJAXYWTLXSKAUS00003
    XBAUOLWLFVVQSBKKC00005
    ULRVFNKZIOWBUGGVL00004
    NIXDTLKKNBSUMITOA00003
    WVEEALFWNCNLWRAYR00001
    % wc -l file
      600000 file
    

    即,120,000 个不同的 17 个字母前缀,以随机顺序附加 01 - 05。

    如果你想要自己的版本,这里是测试脚本:

    perl -le 'for (1..120000) {print map { (q(A)..q(Z))[rand(26)] } 1 .. 17} ' | awk '{for (i=1; i<6; i++) printf ("%s%05i\n", $0, i)}' | awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}'| sort -n | cut -c8- > /tmp/test/file
    

    如果我运行这个:

    % time awk -v  FIELDWIDTHS="17"  '{print > $1".txt"}' file
    

    好吧,大约 15 分钟后我放弃了。

    您可以这样做:

    % time awk -v  FIELDWIDTHS="17"  '{of=$1 ".txt"; if (of in seen){ print >>of } else {print >of; seen[of]; } close(of);}' file
    

    您询问了 Perl,这里有一个类似的 Perl 程序,速度非常快:

    perl -lne '$p=unpack("A17", $_); if ($seen{$p}) { open(fh, ">>", "$p.txt"); print fh $_;} else { open(fh, ">", "$p.txt"); $seen{$p}++; }close fh' file
    

    这是一个将 Ed 的 awk 与这些进行比较的小脚本:

    #!/bin/bash
    
    # run this in a clean directory Luke!
    
    perl -le 'for (1..12000) {print map { (q(A)..q(Z))[rand(26)] } 1 .. 17} ' 
    | awk '{for (i=1; i<6; i++) printf ("%s%05i\n", $0, i)}' 
    | awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}'
    | sort -n 
    | cut -c8- > file.txt
    
    wc -l file.txt
    
    #awk -v  FIELDWIDTHS="17"  '{cnt[$1]++} END{for (e in cnt) print e, cnt[e]}' file
    echo "abd awk"
    time awk -v  FIELDWIDTHS="17"  '{of=$1 ".txt"; if (of in seen){ print >>of } else {print >of; seen[of]; } close(of);}' file.txt
    
    echo "abd Perl"
    time perl -lne '$p=unpack("A17", $_); if ($seen{$p}) { open(fh, ">>", "$p.txt"); print fh $_;} else { open(fh, ">", "$p.txt"); $seen{$p}++; }close fh' file.txt
    
    echo "Ed 1"
    time sort file.txt |
    awk '{out=substr($0,1,17)".txt"} out != prev{close(prev); prev=out} {print > out}'
    
    echo "Ed 2"
    time sort file.txt | awk '{out=substr($0,1,17)".txt"} out != prev{close(prev); prev=out} {print > out}'
    
    echo "Ed 3"
    time awk '{print substr($0,1,17)".txt", NR, $0}' file.txt | sort -k1,1 -k2,2n | awk '$1 != prev{close(prev); prev=$1} {print $3 > $1}'
    

    哪些打印:

       60000 file.txt
    abd awk
    
    real    0m3.058s
    user    0m0.329s
    sys 0m2.658s
    abd Perl
    
    real    0m3.091s
    user    0m0.332s
    sys 0m2.600s
    Ed 1
    
    real    0m1.158s
    user    0m0.174s
    sys 0m0.992s
    Ed 2
    
    real    0m1.069s
    user    0m0.175s
    sys 0m0.932s
    Ed 3
    
    real    0m1.174s
    user    0m0.275s
    sys 0m0.946s
    

    【讨论】:

    • 您介意分享您为创建测试输入文件而编写的脚本(或根据您的输入测试我的答案中的 2 个脚本并发布结果)吗?我假设您正在执行第 3 次运行计时以消除任何缓存影响。
    • 这不是我最漂亮的 ;-)。但我会补充答案。
    • 幸运的是,“漂亮”并不是一次性代码生成临时文件的目标,特别是如果它可以节省我们其他人为自己设计一个文件的时间:-)。谢谢!
    • 你介意在你的盒子上测试我的两个脚本的时间吗?我想发布我和你的时间,以便在相同输入的同一个盒子上进行比较,但在我的笔记本电脑上,你的已经运行了几分钟,所以我失去了尝试更多次的意愿避免缓存问题。
    • 完成。你的更快 ;-) 在我的机器上注意:它是 16 核 Mac Pro,在 RAID 0 中配备 6 个 SSD。不一定具有代表性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-11
    • 2014-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-21
    • 1970-01-01
    相关资源
    最近更新 更多