为什么"啜饮"文件不是普通文本文件I/O的好习惯,何时有用?
例如,为什么我不应该使用这些?
File.read('/path/to/text.txt').lines.each do |line|
# do something with a line
end
Run Code Online (Sandbox Code Playgroud)
要么
File.readlines('/path/to/text.txt').each do |line|
# do something with a line
end
Run Code Online (Sandbox Code Playgroud)
the*_*Man 77
我们一次又一次地看到有关读取文本文件以逐行处理的问题,这些文本文件使用read或者readlines在一个操作中将整个文件拉入内存中.
文档read说:
打开文件,可选地寻找给定的偏移量,然后返回长度字节(默认为文件的其余部分).[...]
将由name指定的整个文件读取为单独的行,并在数组中返回这些行.[...]
拉入一个小文件并不是什么大不了的事,但是随着传入数据缓冲区的增长,内存不得不随机转移,这会占用CPU时间.此外,如果数据占用太多空间,操作系统必须参与以保持脚本运行并开始假脱机到磁盘,这将使程序瘫痪.在HTTPd(Web主机)或需要快速响应的东西上,它将削弱整个应用程序.
Slurping通常是基于对文件I/O速度的误解,或者认为最好先读取缓冲区然后再拆分缓冲区,而不是一次读取一行.
这里有一些测试代码来演示"啜饮"引起的问题.
将其保存为"test.sh":
echo Building test files...
yes "abcdefghijklmnopqrstuvwxyz 123456890" | head -c 1000 > kb.txt
yes "abcdefghijklmnopqrstuvwxyz 123456890" | head -c 1000000 > mb.txt
yes "abcdefghijklmnopqrstuvwxyz 123456890" | head -c 1000000000 > gb1.txt
cat gb1.txt gb1.txt > gb2.txt
cat gb1.txt gb2.txt > gb3.txt
echo Testing...
ruby -v
echo
for i in kb.txt mb.txt gb1.txt gb2.txt gb3.txt
do
echo
echo "Running: time ruby readlines.rb $i"
time ruby readlines.rb $i
echo '---------------------------------------'
echo "Running: time ruby foreach.rb $i"
time ruby foreach.rb $i
echo
done
rm [km]b.txt gb[123].txt
Run Code Online (Sandbox Code Playgroud)
它创建了五个不断增加的文件.1K文件易于处理,非常常见.过去,1MB文件被认为是大文件,但现在它们很常见.1GB在我的环境中很常见,并且会定期遇到超过10GB的文件,因此了解1GB及以上的情况非常重要.
将其保存为"readlines.rb".它没有做任何事情,只是在内部逐行读取整个文件,并将其附加到一个然后返回的数组中,看起来它很快,因为它全部用C语言编写:
lines = File.readlines(ARGV.shift).size
puts "#{ lines } lines read"
Run Code Online (Sandbox Code Playgroud)
将其保存为"foreach.rb":
lines = 0
File.foreach(ARGV.shift) { |l| lines += 1 }
puts "#{ lines } lines read"
Run Code Online (Sandbox Code Playgroud)
sh ./test.sh我的笔记本电脑上运行得到:
Building test files...
Testing...
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
Run Code Online (Sandbox Code Playgroud)
阅读1K文件:
Running: time ruby readlines.rb kb.txt
28 lines read
real 0m0.998s
user 0m0.386s
sys 0m0.594s
---------------------------------------
Running: time ruby foreach.rb kb.txt
28 lines read
real 0m1.019s
user 0m0.395s
sys 0m0.616s
Run Code Online (Sandbox Code Playgroud)
读取1MB文件:
Running: time ruby readlines.rb mb.txt
27028 lines read
real 0m1.021s
user 0m0.398s
sys 0m0.611s
---------------------------------------
Running: time ruby foreach.rb mb.txt
27028 lines read
real 0m0.990s
user 0m0.391s
sys 0m0.591s
Run Code Online (Sandbox Code Playgroud)
读取1GB文件:
Running: time ruby readlines.rb gb1.txt
27027028 lines read
real 0m19.407s
user 0m17.134s
sys 0m2.262s
---------------------------------------
Running: time ruby foreach.rb gb1.txt
27027028 lines read
real 0m10.378s
user 0m9.472s
sys 0m0.898s
Run Code Online (Sandbox Code Playgroud)
阅读2GB文件:
Running: time ruby readlines.rb gb2.txt
54054055 lines read
real 0m58.904s
user 0m54.718s
sys 0m4.029s
---------------------------------------
Running: time ruby foreach.rb gb2.txt
54054055 lines read
real 0m19.992s
user 0m18.765s
sys 0m1.194s
Run Code Online (Sandbox Code Playgroud)
阅读3GB文件:
Running: time ruby readlines.rb gb3.txt
81081082 lines read
real 2m7.260s
user 1m57.410s
sys 0m7.007s
---------------------------------------
Running: time ruby foreach.rb gb3.txt
81081082 lines read
real 0m33.116s
user 0m30.790s
sys 0m2.134s
Run Code Online (Sandbox Code Playgroud)
请注意readlines,每次文件大小增加时,运行速度会慢两倍,并且foreach线性减速.在1MB时,我们可以看到影响"啜饮"I/O的东西不会影响逐行读取.而且,因为现在1MB文件很常见,所以如果我们不提前考虑,很容易看到它们会在程序的整个生命周期内减慢文件的处理速度.这里或几秒钟它们发生一次并不多,但如果它们每分钟发生多次,则会在一年结束时产生严重的性能影响.
几年前我处理大数据文件时遇到了这个问题.我正在使用的Perl代码会在加载文件时重新分配内存时会定期停止.重写代码不会淹没数据文件,而是逐行读取和处理它,从超过五分钟的速度提高到小于一的速度,并教会了我一个重要的教训.
"啜饮"文件有时是有用的,特别是如果你必须跨越界限做一些事情,但是,如果必须这样做,那么花一些时间考虑阅读文件的替代方法是值得的.例如,考虑维护从最后"n"行构建的小缓冲区并扫描它.这样可以避免因尝试读取和保存整个文件而导致的内存管理问题.这在Perl相关博客" Perl Slurp-Eaze " 中进行了讨论,该博客涵盖了"whens"和"whys"以证明使用完整文件读取,并且适用于Ruby.
出于其他不要"捏造"文件的优点,请阅读" 如何搜索文件文本以获取模式并将其替换为给定值 ".
这有点老了,但令我有点惊讶的是,没有人提到,吞食输入文件会使程序对管道几乎毫无用处。在管道中,输入文件可能很小但很慢。如果您的程序正在吞食,则意味着它无法在数据可用时处理数据,而是让您等待输入完成所需的时间。多久?grep如果我在一个大的层次结构中执行或操作,那么它可以是任何东西,例如几小时或几天,或多或少find。它也可以设计为不完整,就像无限文件一样。例如,无论journalctl -f系统发生什么事件,都会继续输出,不会停止;tshark将不间断地输出它在网络中看到的任何情况;ping将继续 ping 而不停止。/dev/zero是无限的,/dev/urandom是无限的。
我唯一一次认为 slurping 是可以接受的可能是在配置文件中,因为程序在完成读取之前可能无法执行任何操作。
| 归档时间: |
|
| 查看次数: |
9757 次 |
| 最近记录: |