简要版: 如何在Ruby中从STDIN(或文件)逐个字符地读取数据,同时保持高性能?(虽然问题可能不是特定于Ruby的)
详细版: 在学习Ruby时,我正在设计一个小工具,它必须从管道文本数据中读取数据,查找并收集其中的数字并进行一些处理。
cat huge_text_file.txt | program.rb
input > 123123sdas234sdsd5a ...
output > 123123, 234, 5, ...
文本输入可能非常大(以GB计),可能不包含换行符或空格(任何非数字字符都是分隔符),因此我进行了逐个字符的读取(尽管我对性能有所担忧),结果发现这种方式非常慢。
仅仅对一个900KB的输入文件进行逐个字符的读取,需要大约7秒钟的时间!
while c = STDIN.read(1)
end
如果我输入包含换行符的数据并逐行读取,同一文件的读取速度会快100倍。
while s = STDIN.gets
end
似乎使用
STDIN.read(1)
从管道读取数据时不涉及任何缓冲,每次读取都会访问硬盘 - 但是操作系统难道不应该对其进行缓存吗?
STDIN.gets
不是内部逐个字符读取直到遇到 '\n
' 吗?使用 C 时,我可能会以块的形式读取数据,虽然我必须处理数字被缓冲窗口分割的情况,但这看起来并不像 Ruby 的优雅解决方案。那么正确的方法是什么呢? P.S 在 Python 中计时读取相同文件:
for line in f:
line
f.close()
运行时间为0.01秒。
c = f.read(1)
while c:
c = f.read(1)
f.close()
运行时间为0.17秒。
谢谢!
read(1)
将为文件的每个字节创建一个新的字符串对象。如果您创建一个临时字符串并在每次调用read
时重复使用它,您可能会获得更好的结果。因此,如果您首先执行buffer = ""
,则可以在循环中调用STDIN.read(1, buffer)
。 - mattcat
管道传输的 900K 文件,STDIN.read(1)
平均运行时间为 4.6 秒,buffer=''; STDIN.read(1,buffer)
为 4.5 秒,STDIN.gets
为 0.08 秒。我还尝试将所有内容从 SSD 移动到 HDD,看看是否有影响 - 没有。我认为输入文件必须由操作系统缓存,并且硬盘实际上并没有每次读取。 - epsylonfor line in file: line
的运行时间为0.01秒,而while c: c=file.read(1)
则为0.17秒。尽管如此,按块读取仍然比它快10倍以上。 - epsyloneach_byte
代替while
循环:STDIN.each_byte do |b| ... end
。这种方式似乎更快,但我不太清楚原因。 - matteach_char
,需要考虑编码)。 - mattread(1)
快10倍!如果您将其发布为答案,我会接受它。最终我还是使用了分块读取(并处理分割的数字),因为使用大缓冲区似乎是最快的方式。 - epsylon