为什么我的awk脚本比head + tail脚本慢得多?

问题描述

我想分割一个大文件(big.txt)。通过给定的行号。例如,如果给定编号为10 15 30,我将得到big.txt的4个文件:1-1011-1516-3030 - EOF

解决问题对我来说不是挑战,我写了3种不同的解决方案。但是,我无法解释其性能。为什么awk脚本最慢。 ( GNU Awk

对于big.txt,我刚刚做了seq 1.5billion > big.txt (~15Gb)

首先,头部和尾巴:

INPUT_FILE="big.txt"  # The input file
LINE_NUMBERS=( 400000 700000 1200000 ) # Given line numbers
START=0                 # The offset to calculate lines
IDX=1                   # The index used in the name of generated files: file1,file2 ...

for i in "${LINE_NUMBERS[@]}"
do
    # Extract the lines
    head -n $i "$INPUT_FILE" | tail -n +$START > "file$IDX.txt"
    #
    (( IDX++ ))
    START=$(( i+1 ))
done

# Extract the last given line - last line in the file
tail -n +$START "$INPUT_FILE" > "file$IDX.txt"

第二个:sed:

INPUT_FILE="big.txt"  # The input file
LINE_NUMBERS=( 400000 700000 1200000 ) # Given line numbers
START=1                 # The offset to calculate lines
IDX=1                   # The index used in the name of generated files: file1,file2 ...

for i in "${LINE_NUMBERS[@]}"
do
    T=$(( i+1 ))
    # Extract the lines using sed command
    sed -n -e " $START,$i p" -e "$T q" "$INPUT_FILE" > "file$IDX.txt"
    (( IDX++ ))
    START=$T
done

# Extract the last given line - last line in the file
sed -n "$START,$ p" "$INPUT_FILE" > "file$IDX.txt"

最后一个,awk

awk -v nums="400000 700000 1200000" 'BEGIN{c=split(nums,a)} {
    for(i=1; i<=c; i++){
        if( NR<=a[i] ){
            print > "file" i ".txt"
            next
        }
    }
    print > "file" c+1 ".txt"
}' big.txt

根据我的测试(使用time命令),头+尾巴是最快的:

real 73.48
user 1.42
sys 17.62

sed 之一:

real 144.75
user 105.68
sys 15.58

确认之一:

real 234.21
user 187.92
sys 3.98

awk仅浏览了一次文件,为什么它比其他两个文件慢得多?另外,我认为尾巴和头部将是最慢的解决方案,为什么这么快?我想这可能与awk的重定向有关? (打印>文件)

有人可以向我解释吗?谢谢。

解决方法

awk可以比headtail快吗?

否,这会变慢,至少对于大型输入文件而言,对于合理数量的块而言,它将变慢。因为它将读取每一行并进行一些处理。另一方面,headtail将大量读取换行符,而不进行任何操作,直到找到自变量提供的行号为止。然后,他们不必再逐行阅读并决定要做什么,而是可以像cat那样转储内容。

如果我们增加块的数量,如果分割行号的数组越来越大,那么我们将达到可以克服许多headtail进程调用成本的地步一个awk进程的成本,此后awk会更快。


awk脚本改进

由于该循环,此awk很慢!只是认为对于最后一个输出文件,对于要打印的每一行,我们运行4次迭代,直到打印出该行。当然,时间复杂度仍然与输入保持线性关系,但是随着输入的增加,所有这些检查和分配都有可以观察到的成本。它可以大大改善,例如像这样:

> cat tst.awk
BEGIN { 
    a[1]
    a[40000]
    a[70000]
    a[120000]
}

NR in a {
    close(out)
    out = "file" ++i ".txt"
}

{ print > out }

在这里,我们每行仅测试NR,实际上我们几乎只打印。

awk -f tst.awk big.txt

测试

这是一些基本测试,我做了一个文件,不是很大,有520万行。

> wc -l big.txt 
5288558 big.txt

现在,有了该循环,在何处分割文件确实很重要!如果必须将大多数行写到最后一个块中,则意味着迭代次数更多,因此速度较慢

> head -1 test.sh
awk -v nums="100000 200000 300000" 'BEGIN{c=split(nums,a)} {
> time sh test.sh

real    0m10.960s
user    0m10.823s
sys     0m0.066s

如果大多数行都转到第一个文件(这意味着一个迭代然后是下一个),它将变得更快!

> head -1 test.sh
awk -v nums="5000000 5100000 5200000" 'BEGIN{c=split(nums,a)} {
> time sh test.sh

real    0m6.914s
user    0m6.838s
sys     0m0.043s

通过上述修改,无论切点是什么,它都应该足够快。

> time awk -f tst.awk big.txt 

real    0m4.270s
user    0m4.185s
sys     0m0.048s
,

对于awk,每一行都需要循环,比较并创建文件名。也许awk还要执行解析每一行的艰巨任务。

您可能要尝试以下实验

  • 尝试执行mawk(awk的快速实现),并检查它是否更快。
  • 删除print > "file" i ".txt"可以节省多少时间。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...