为什么BufferedReader read()比readLine()慢得多?
我需要一次读取一个字符的文件,并使用BufferedReader
的read()
方法。 *
我发现read()
比readLine()
慢10倍左右。 这是预期的吗? 还是我做错了什么?
这是Java 7的基准testing。inputtesting文件有大约500万行和254万字符(〜242 MB)**:
read()
方法需要大约7000 ms来读取所有字符:
@Test public void testRead() throws IOException, UnindexableFastaFileException{ BufferedReader fa= new BufferedReader(new FileReader(new File("chr1.fa"))); long t0= System.currentTimeMillis(); int c; while( (c = fa.read()) != -1 ){ // } long t1= System.currentTimeMillis(); System.err.println(t1-t0); // ~ 7000 ms }
readLine()
方法只需要readLine()
ms:
@Test public void testReadLine() throws IOException{ BufferedReader fa= new BufferedReader(new FileReader(new File("chr1.fa"))); String line; long t0= System.currentTimeMillis(); while( (line = fa.readLine()) != null ){ // } long t1= System.currentTimeMillis(); System.err.println(t1-t0); // ~ 700 ms }
* 实用目的 :我需要知道每行的长度,包括换行符( \n
或\r\n
)和剥离后的行长。 我还需要知道一行是否以>
字符开头。 对于给定的文件,这只在程序开始时执行一次。 由于EOL字符不是由BufferedReader.readLine()
返回我正在使用read()
方法。 如果有更好的方法,请说。
** gzip文件在这里http://hgdownload.cse.ucsc.edu/goldenpath/hg19/chromosomes/chr1.fa.gz 。 对于那些可能想知道的,我正在写一个类来索引fasta文件。
分析性能时最重要的事情就是在开始之前有一个有效的基准。 那么让我们从一个简单的JMH基准开始,它显示了我们预热后的预期性能。
我们必须考虑的一件事是,由于现代操作系统喜欢caching经常访问的文件数据,所以我们需要一些方法来清除testing之间的caching。 在Windows上有一个小工具可以做到这一点 – 在Linux上,你应该可以通过在某处写入一些伪文件来实现。
代码如下所示:
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Mode; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; @BenchmarkMode(Mode.AverageTime) @Fork(1) public class IoPerformanceBenchmark { private static final String FILE_PATH = "test.fa"; @Benchmark public int readTest() throws IOException, InterruptedException { clearFileCaches(); int result = 0; try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) { int value; while ((value = reader.read()) != -1) { result += value; } } return result; } @Benchmark public int readLineTest() throws IOException, InterruptedException { clearFileCaches(); int result = 0; try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) { String line; while ((line = reader.readLine()) != null) { result += line.chars().sum(); } } return result; } private void clearFileCaches() throws IOException, InterruptedException { ProcessBuilder pb = new ProcessBuilder("EmptyStandbyList.exe", "standbylist"); pb.inheritIO(); pb.start().waitFor(); } }
如果我们运行它
chcp 65001 # set codepage to utf-8 mvn clean install; java "-Dfile.encoding=UTF-8" -server -jar .\target\benchmarks.jar
我们得到以下结果(需要大约2秒来清除caching对我来说,我在硬盘上运行,所以这是为什么它比你慢很多):
Benchmark Mode Cnt Score Error Units IoPerformanceBenchmark.readLineTest avgt 20 3.749 ± 0.039 s/op IoPerformanceBenchmark.readTest avgt 20 3.745 ± 0.023 s/op
惊喜! 正如所料,在JVM进入稳定模式后,这里完全没有性能差异。 但是readCharTest方法有一个exception点:
# Warmup Iteration 1: 6.186 s/op # Warmup Iteration 2: 3.744 s/op
这是你看到的问题的可能性。 我能想到的最可能的原因是OSR在这里做得不好,或者JIT只是跑得太晚而不能在第一次迭代中做出改变。
根据您的使用情况,这可能是一个大问题或可以忽略不计(如果您正在阅读一千个文件,这将无所谓,如果你只读一个这是一个问题)。
解决这样的问题并不容易,也没有一般的解决scheme,尽pipe有办法解决这个问题。 一个简单的testing,看看我们是否在正确的轨道上运行的代码与-Xcomp
选项迫使HotSpot编译第一次调用的每个方法。 而且确实这样做会导致第一次调用的大的延迟消失:
# Warmup Iteration 1: 3.965 s/op # Warmup Iteration 2: 3.753 s/op
可能的scheme
现在我们有一个好主意是什么实际问题是(我的猜测仍然是所有这些锁既不合并,也没有使用有效的偏见锁实现),解决scheme是非常简单和简单的:减less函数调用的数量(所以是我们可以在没有上述任何条件的情况下达到这个解决scheme,但是总是能够很好地处理这个问题,并且可能有一个解决scheme不涉及更改代码)。
下面的代码一直运行的速度比其他任何一个都快 – 你可以玩数组大小,但它是惊人的不重要(可能是因为与其他方法相反, read(char[])
不需要获得一个锁,所以每次调用的成本开始时较低)。
private static final int BUFFER_SIZE = 256; private char[] arr = new char[BUFFER_SIZE]; @Benchmark public int readArrayTest() throws IOException, InterruptedException { clearFileCaches(); int result = 0; try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) { int charsRead; while ((charsRead = reader.read(arr)) != -1) { for (int i = 0; i < charsRead; i++) { result += arr[i]; } } } return result; }
这很可能是足够好的性能明智的,但如果你想提高性能甚至进一步使用文件映射可能(在这种情况下,不会有太大的改进,但如果你知道你的文本总是ASCII ,你可以进一步优化)进一步帮助性能。
感谢@Voo的更正。 我在下面提到的是正确的从FileReader#read()
v / s BufferedReader#readLine()
的观点,但不正确从BufferedReader#read()
v / s BufferedReader#readLine()
angular度来看,所以我已经striked-答案。
在 BufferedReader
上使用read()
方法不是一个好主意,它不会造成任何伤害,但肯定会浪费课程的目的。
BufferedReader
在生活中的全部目的是通过缓冲内容来减lessI / O。 你可以在这里阅读Java教程。 你也可能注意到BufferedReader
中的read()
方法实际上是从Reader
inheritance的,而readLine()
是BufferedReader
自己的方法。
如果你想使用read()
方法,那么我会说你最好使用FileReader
,这是为了这个目的。 你可以在这里阅读 Java教程。
所以, 我认为回答你的问题是非常简单的 (没有进入基准testing和所有的解释)
- 每个
read()
都由底层操作系统处理,并触发磁盘访问,networking活动或其他相对昂贵的操作。 - 当你使用
readLine()
那么你保存所有这些开销,所以readLine()
将总是比read()
快,可能不是小数据,但更快。
所以这是我自己的问题的实际答案:不要使用BufferedReader.read()
而是使用FileChannel
。 (显然我不回答为什么我把标题)。 这里是快速和肮脏的基准,希望别人会觉得它有用:
@Test public void testFileChannel() throws IOException{ FileChannel fileChannel = FileChannel.open(Paths.get("chr1.fa")); long n= 0; int noOfBytesRead = 0; long t0= System.nanoTime(); while(noOfBytesRead != -1){ ByteBuffer buffer = ByteBuffer.allocate(10000); noOfBytesRead = fileChannel.read(buffer); buffer.flip(); while ( buffer.hasRemaining() ) { char x= (char)buffer.get(); n++; } } long t1= System.nanoTime(); System.err.println((float)(t1-t0) / 1e6); // ~ 250 ms System.err.println("nchars: " + n); // 254235640 chars read }
用char〜char整个文件读取〜250 ms,这个策略比BufferedReader.readLine()
(〜700 ms)快很多,更不用说read()
。 在循环中添加if语句以检查x == '\n'
和x == '>'
没有什么区别。 另外把一个StringBuilder
重构线不会影响太多的时间。 所以这对我来说是很好的(至less现在是这样)。
感谢@ Marco13提及FileChannel。
如果您仔细考虑,看到这种差异就不足为奇了。 一个testing是迭代文本文件中的行,而另一个是迭代字符。
除非每行都包含一个字符,否则预期readLine()
比read()
方法快(尽pipe正如上面的注释所指出的那样,由于BufferedReader缓冲input,而物理文件读取可能不是唯一的表演服务)
如果你真的想testing两者之间的差异,我会build议在两个testing中迭代每个字符的设置。 例如:
void readTest(BufferedReader r) { int c; StringBuilder b = new StringBuilder(); while((c = r.read()) != -1) b.append((char)c); } void readLineTest(BufferedReader r) { String line; StringBuilder b = new StringBuilder(); while((line = b.readLine())!= null) for(int i = 0; i< line.length; i++) b.append(line.charAt(i)); }
除此之外,请使用“Java性能诊断工具”来testing您的代码。 另外,请阅读关于如何微调Java代码的基础知识 。
Java JIT优化了空循环体,所以你的循环看起来像这样:
while((c = fa.read()) != -1);
和
while((line = fa.readLine()) != null);
我build议你阅读这里的基准和这里的循环优化。
至于为什么花费的时间不同:
-
原因之一(这只适用于循环的主体包含代码):在第一个例子中,你每行做一个操作,在第二个,你做每个字符一个。 这增加了你有更多的线/字符。
while((c = fa.read()) != -1){ //One operation per character. } while((line = fa.readLine()) != null){ //One operation per line. }
-
原因二:在类
BufferedReader
,readLine()
方法在后台不使用read()
– 它使用自己的代码。readLine()
方法读取一行的每个字符所需的操作量要less于read()
方法所需的read()
行数,这就是readLine()
在读取整个文件时速度更快的原因。 -
原因三:读取每个字符需要更多的迭代,而不是读取每一行(除非每个字符都在一个新行上)。
read()
被调用的次数比readLine()
。
- unit testing与functiontesting
- 在RSpec中如何说“any_instance”“should_receive”任意次数
- node.js与ASP.NET Core性能testing的意外结果
- 在jUnit 4.x之前和之后套件执行挂钩
- 如何用JUnit Test注解来断言我的exception消息?
- 我如何testing本地主机上的Facebook“赞”button?
- Swift 3 – 比较date对象
- angular2 testing:无法绑定到'ngModel',因为它不是'input'的已知属性
- Haskell函数可以certificate/模型检查/validation的正确性属性?