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

Why is my awk script much slower than the head+tail script?

我想拆分一个巨大的文件 (big.txt)。通过给定的行号。例如,如果给出的数字是 10 15 30,我将得到 4 个文件:1-1011-1516-30 和 big.txt 的 30 - 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 命令),head+tail 是最快的:

real 73.48
user 1.42
sys 17.62

sed一个:

real 144.75
user 105.68
sys 15.58

awk一个:

real 234.21
user 187.92
sys 3.98

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

谁能给我解释一下?谢谢。

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

您可能想尝试以下实验

  • 试试 mawk(awk 的快速实现)并检查它是否快得多。
  • 删除 print > "file" i ".txt" 看看它节省了多少时间。

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

测试

这是一些基本测试,我做了一个文件,不大,5.2M 行。

> 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