问题描述
我想分割一个大文件(big.txt)。通过给定的行号。例如,如果给定编号为10 15 30
,我将得到big.txt的4个文件:1-10
,11-15
,16-30
和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
命令),头+尾巴是最快的:
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
可以比head
和tail
快吗?
否,这会变慢,至少对于大型输入文件而言,对于合理数量的块而言,它将变慢。因为它将读取每一行并进行一些处理。另一方面,head
和tail
将大量读取换行符,而不进行任何操作,直到找到自变量提供的行号为止。然后,他们不必再逐行阅读并决定要做什么,而是可以像cat
那样转储内容。
如果我们增加块的数量,如果分割行号的数组越来越大,那么我们将达到可以克服许多head
和tail
进程调用成本的地步一个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"
可以节省多少时间。