在文件中打印单行的最快方法
Posted
技术标签:
【中文标题】在文件中打印单行的最快方法【英文标题】:Fastest way to print a single line in a file 【发布时间】:2013-03-15 23:45:32 【问题描述】:我必须从一个大文件中提取一个特定的行(1500000 行),在多个文件的循环中多次,我在问自己最好的选择是什么 (在性能方面)。 有很多方法可以做到这一点,我主要使用这 2 个
cat $file | head -1
或
cat $file | sed -n '1p'
我找不到答案,他们是否都只获取第一行或两个(或两者)之一首先打开整个文件,然后获取第 1 行?
【问题讨论】:
使用time
测量命令。
为什么将cat
输入到工具中?他们都可以自己打开文件,如果您担心效率,他们可能会做得更好。但是,是的,管道应该只“流式传输”文件的前几个块(然后注意到消费者不再关心)。
顺便说一句,对于大文件中的特定行,使用提前编译语言的程序很可能比head "-$pos" "$file" | tail -1
运行得更快。 (像 C 一样,特别是使用 SIMD 内在函数来优化大内存块上换行符的计数,直到您接近正确的起始位置。如果在页面缓存中已经很热,它应该只在映射文件后受内存带宽的限制.)
【参考方案1】:
我进行了广泛的测试,发现如果您想要文件的每一行:
while IFS=$'\n' read LINE; do
echo "$LINE"
done < your_input.txt
比任何其他(基于 Bash 的)方法快得多。所有其他方法(如sed
)每次都读取文件,至少到匹配的行。如果文件有 4 行长,您将得到:1 -> 1,2 -> 1,2,3 -> 1,2,3,4
= 10
读取,而 while 循环只维护一个位置光标(基于IFS
),因此总共只会读取4
。
在具有 ~15k 行的文件上,差异是惊人的:~25-28 秒(基于sed
,每次提取特定行)与 ~0-1 秒(基于while...read
,通读归档一次)
上面的示例还展示了如何以更好的方式将IFS
设置为换行符(感谢下面 cmets 的 Peter),这有望解决有时在 Bash 中使用 while... read ...
时出现的一些其他问题.
【讨论】:
echo $line
应该是 echo "$line"
以避免分词。或者更好的是,printf "%s" "$line"
即使使用像 -e
这样的行也是安全的。是的,我认为你想要(IFS=$'\n'; read line; printf "%s" "$line")
,尽管它分叉了一个子shell,所以如果IFS=$'\n' read line < file
工作而无需保存/恢复IFS shell 变量,你可能只对read
使用覆盖IFS。
感谢彼得的输入!这让我进一步测试,我发现了一些非常有趣的东西,这在逻辑上也是有道理的。参考上面。
现在你正在打印整个文件(除了像 "-e"
这样的行,它会回显会吃掉或抛出错误),所以你的循环可以用 cat "$file"
替换,这又是很多比bash
读取循环快。这个问题是关于提取 single 行的,这意味着您不希望它在循环中按顺序重复每一行。如果你只是想为输入文件或流的每一行运行一些 bash 命令(即不同的循环体),是的,你当然会这样做。
但这不太可能是从大文件中获取just第 100k 行的最快方法,而这是其他答案试图有效地做到的。
是的,我就是这么说的。这是处理每一行行的最快方法,但这与问题所问的问题(以及其他答案所回答的问题)不同。他们只是在 sed 或 head|tail 上使用重复循环来获得足够长的时间来测量,而不是因为他们实际上想要一系列行。你的答案属于Looping through the content of a file in Bash,除了它已经用while read
循环回答。 (并使用安全的 printf 而不是不安全的 echo 作为正文)。【参考方案2】:
如果您只想从一个大文件中打印 1 行(比如第 20 行),您也可以这样做:
head -20 filename | tail -1
我用 bash 做了一个“基本”测试,它似乎比上面的 sed -n '1p;q
解决方案表现得更好。
测试获取一个大文件并从中间某处打印一行(在10000000
行),重复 100 次,每次选择下一行。所以它选择行10000000,10000001,10000002, ...
等等直到10000099
$wc -l english
36374448 english
$time for i in 0..99; do j=$((i+10000000)); sed -n $j'p;q' english >/dev/null; done;
real 1m27.207s
user 1m20.712s
sys 0m6.284s
对比
$time for i in 0..99; do j=$((i+10000000)); head -$j english | tail -1 >/dev/null; done;
real 1m3.796s
user 0m59.356s
sys 0m32.376s
用于从多个文件中打印一行
$wc -l english*
36374448 english
17797377 english.1024MB
3461885 english.200MB
57633710 total
$time for i in english*; do sed -n '10000000p;q' $i >/dev/null; done;
real 0m2.059s
user 0m1.904s
sys 0m0.144s
$time for i in english*; do head -10000000 $i | tail -1 >/dev/null; done;
real 0m1.535s
user 0m1.420s
sys 0m0.788s
【讨论】:
单个sed
调用对于低行位置稍快一些,例如i + 1000
。请参阅@roel's answer 和我的 cmets:对于像 100k 这样的大行位置,我可以重现与您非常相似的结果,并且还确认 Roel 的结果,对于较短的计数,单独使用 sed
更好。 (对我来说,在 i7-6700k 桌面 Skylake 上,head|tail 甚至比你更好,大 n 的相对加速更大。可能比你测试的系统更好的内核间带宽,因此管道所有数据的成本更低。) 【参考方案3】:
放弃对cat
的无用使用并执行以下操作:
$ sed -n '1p;q' file
这将在打印行后退出sed
脚本。
基准测试脚本:
#!/bin/bash
TIMEFORMAT='%3R'
n=25
heading=('head -1 file' 'sed -n 1p file' "sed -n '1p;q file" 'read line < file && echo $line')
# files upto a hundred million lines (if your on slow machine decrease!!)
for (( j=1; j<=100,000,000;j=j*10 ))
do
echo "Lines in file: $j"
# create file containing j lines
seq 1 $j > file
# initial read of file
cat file > /dev/null
for comm in 0..3
do
avg=0
echo
echo $heading[$comm]
for (( i=1; i<=$n; i++ ))
do
case $comm in
0)
t=$( time head -1 file > /dev/null; 2>&1);;
1)
t=$( time sed -n 1p file > /dev/null; 2>&1);;
2)
t=$( time sed '1p;q' file > /dev/null; 2>&1);;
3)
t=$( time read line < file && echo $line > /dev/null; 2>&1);;
esac
avg=$avg+$t
done
echo "scale=3;($avg)/$n" | bc
done
done
只需保存为benchmark.sh
并运行bash benchmark.sh
。
结果:
head -1 file
.001
sed -n 1p file
.048
sed -n '1p;q file
.002
read line < file && echo $line
0
**1,000,000 行文件的结果。*
所以sed -n 1p
的时间将随着文件的长度线性增长,但其他变化的时间将是恒定的(并且可以忽略不计),因为它们都在读取第一行后退出:
注意:由于在更快的 Linux 机器上,时间与原始帖子不同。
【讨论】:
或者可能是sed 1q file
不太忙。
@potong 我使用了这种格式,所以我可以用来打印文件中的任何一行。
理想情况下,您应该每次都重新创建文件。根据文件系统的不同,缓存会影响时序,以便第一次运行真正的 I/O 和后续运行受益。
+1 用于详细的性能比较。顺便说一句,在您的脚本中,case
和 heading
中的 sed 行 (sed 1q
) 是不同的。 :) 最好使它们相同,特别是对于性能测试。无论如何,很好的答案!
@Kent 好地方,在我测试和更新时滑过。还添加了一个漂亮的图表!【参考方案4】:
如果您真的只是获取第一行并读取数百个文件,那么考虑使用 shell 内置命令而不是外部外部命令,请使用 read
,它是 bash 和 ksh 的内置 shell。这消除了使用awk
、sed
、head
等创建进程的开销。
另一个问题是对 I/O 进行定时性能分析。第一次打开然后读取文件时,文件数据可能没有缓存在内存中。但是,如果您再次对同一文件尝试第二个命令,则数据和 inode 已被缓存,因此计时结果可能会更快,几乎与您使用的命令无关。此外,inode 几乎可以永久缓存。例如,它们在 Solaris 上运行。或者无论如何,几天。
例如 linux 缓存一切和厨房水槽,这是一个很好的性能属性。但是,如果您没有意识到这个问题,它会使基准测试成为问题。
所有这些缓存效果“干扰”都取决于操作系统和硬件。
所以 - 选择一个文件,使用命令读取它。现在它被缓存了。运行相同的测试命令数十次,这是对命令和子进程创建的效果进行采样,而不是您的 I/O 硬件。
这是 sed vs read 10 次迭代,在读取文件一次后获取同一文件的第一行:
sed:sed '1p;q' uopgenl20121216.lis
real 0m0.917s
user 0m0.258s
sys 0m0.492s
阅读:read foo < uopgenl20121216.lis ; export foo; echo "$foo"
real 0m0.017s
user 0m0.000s
sys 0m0.015s
这显然是人为的,但确实显示了内置性能与使用命令之间的差异。
【讨论】:
+1 不错的答案。我已经编辑了我的帖子以包含read
的使用,果然它是最快的(除了偶尔的 0.001 甚至没有注册)。
如何使用“读取”解决方案打印第 n 行?【参考方案5】:
如何避免管道?
sed
和 head
都支持文件名作为参数。这样你就可以避免从猫身边经过。我没有测量它,但是 head 在较大的文件上应该更快,因为它会在 N 行之后停止计算(而 sed 会遍历所有文件,即使它不打印它们 - 除非您指定 q
uit 选项如上所述)。
例子:
sed -n '1p;q' /path/to/file
head -n 1 /path/to/file
再次,我没有测试效率。
【讨论】:
以上是关于在文件中打印单行的最快方法的主要内容,如果未能解决你的问题,请参考以下文章