对于一个有5000万行,每行60个字符(包括结尾的换行符)的文件,使用8K缓冲区,需要进行366211次系统调用才能读取2.8GiB的文件。顺便说一句,在Perl程序读取文本文件花费10分钟时,你可以通过查看任务管理器进程列表中的磁盘I/O读取增量(至少在Windows上),来确认这种行为。
有人在perlmonks上问了关于如何增加Perl输入缓冲区大小的问题,有人回答这里可以通过增加"$/"的大小来增加缓冲区的大小,但是从perldoc中可以得知:
将$/设置为整数引用、包含整数的标量或可转换为整数的标量会尝试读取记录而不是行,最大记录大小为引用的整数。
因此我认为,这并没有实际上增加Perl用于从磁盘预读的缓冲区的大小,当使用典型的:
while(<>) {
#do something with $_ here
...
}
“逐行”成语。
现在,可能会有一种不同的“读取记录并将其解析为行”的代码版本通常更快,并且可以绕过标准习语的根本问题,即无法更改默认缓冲区大小(如果确实不可能),因为您可以将“记录大小”设置为任何您想要的大小,然后将每个记录解析为单独的行,并希望Perl做正确的事情,并最终针对每个记录执行一个系统调用,但它增加了复杂性,而我真正想做的只是通过将上面示例中使用的缓冲区增加到合理大的大小(例如64K),甚至使用测试脚本在我的系统上调整该缓冲区大小以获得长读取的最佳大小,而不需要额外的麻烦来获得简单的性能提升。
在Java中,就直接支持增加缓冲区大小而言,情况要好得多。
在Java中,我认为java.io.BufferedReader使用的当前默认缓冲区大小也是8192字节,尽管JDK文档中的最新参考意见有些暧昧,例如1.5文档仅表示:
有幸的是,使用Java时,您不必相信JDK开发人员已经为您的应用程序做出了正确的决策,可以设置自己的缓冲区大小(在此示例中为64K):
import java.io.BufferedReader;
[...]
reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"), 65536);
[...]
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
/* do something with the line here */
foo(line);
}
即使使用了大缓冲区和现代硬件,每次一行地解析文本也只能挤出有限的性能。我相信可以通过读取多行记录并将每个记录分成标记,然后在每个记录中处理这些标记的方式来获得文件读取的最佳性能,但这会增加复杂性和边界情况(不过如果有一个优雅的纯Java解决方案(仅使用JDK 1.5中存在的特性),那就太棒了)。至少对于Perl而言,增加缓冲区大小可以解决80%的性能问题,同时保持简单明了。
我的问题是:
是否有办法在Perl中调整上述典型的“逐行”惯用语的缓冲区大小,类似于Java示例中增加缓冲区大小的方法?
perldelta
for v5.13.8 表示:“当文件句柄上的方法调用因无法解析该方法而失败,并且未加载IO::File
时,Perl 现在通过require
加载IO::File
并再次尝试方法解析”。IO::File
是IO::Handle
的子类,因此两者都是按需加载的(以及IO::Seekable
),它们的方法可以在没有显式use
语句的情况下使用。具备此功能的第一个公共版本是 2011 年的 Perl v5.14.0。 - Borodin