为什么Java比C ++更快读取大文件?

我有一个2 GB的文件( iputfile.txt ),其中文件中的每一行都是一个单词,就像:

 apple red beautiful smell spark input 

我需要编写一个程序来读取文件中的每个单词并打印出单词数量。 我使用Java和C ++编写,但结果令人惊讶:Java运行速度比C ++快2.3倍。 我的代码如下:

C ++:

 int main() { struct timespec ts, te; double cost; clock_gettime(CLOCK_REALTIME, &ts); ifstream fin("inputfile.txt"); string word; int count = 0; while(fin >> word) { count++; } cout << count << endl; clock_gettime(CLOCK_REALTIME, &te); cost = te.tv_sec - ts.tv_sec + (double)(te.tv_nsec-ts.tv_nsec)/NANO; printf("Run time: %-15.10fs\n", cost); return 0; } 

输出:

 5e+08 Run time: 69.311 s 

Java的:

  public static void main(String[] args) throws Exception { long startTime = System.currentTimeMillis(); FileReader reader = new FileReader("inputfile.txt"); BufferedReader br = new BufferedReader(reader); String str = null; int count = 0; while((str = br.readLine()) != null) { count++; } System.out.println(count); long endTime = System.currentTimeMillis(); System.out.println("Run time : " + (endTime - startTime)/1000 + "s"); } 

输出:

 5.0E8 Run time: 29 s 

在这种情况下,为什么Java比C ++更快,我该如何提高C ++的性能呢?

你没有比较相同的东西。 Java程序读取行,取决于换行符,而C ++程序读取空格分隔的“单词”,这是一个额外的工作。

尝试istream::getline

后来

你也可以尝试做一个基本的读操作来读取一个字节数组,并扫描这个换行符。

甚至更晚

在我的旧的Linux笔记本上,jdk1.7.0_21和别的告诉我这是旧的4.3.3几乎与C ++ getline相比。 (我们已经确定阅读单词比较慢)。-O0和-O2没有什么区别,考虑到循环中代码的简单性,这并不让我感到意外。

Last note正如我所build议的那样,LEN = 1MB的fin.read(buffer,LEN)和使用memchr扫描'\ n'会导致另一个20%左右的速度提高,这使得C(没有任何C ++剩下的现在)比Java更快。

语言处理I / O的方式有很多显着的不同,所有这些都可以以某种方式发挥作用。

也许第一个(也是最重要的)问题是:如何在文本文件中编码数据。 如果是单字节字符( ISO 8859-1或UTF-8 ),则Java在处理之前必须将其转换为UTF-16 ; 取决于语言环境,C ++可能(或可能不)也转换或做一些额外的检查。

正如已经指出的(至less部分),在C ++中, >>使用特定于语言环境的isspacegetline将简单地比较'\n' ,这可能更快。 ( isspace典型实现将使用位图,这意味着每个字符都有一个额外的内存访问。)

优化级别和特定的库实现也可能有所不同。 在C ++中,一个库实现的速度比另一个快2到3倍,这并不罕见。

最后,最显着的区别是:C ++区分了文本文件和二进制文件。 您已经以文本模式打开文件; 这意味着在提取操作员看到它之前,它将在最低级被“预处理”。 这取决于平台:对于Unix平台来说,“预处理”是无操作的; 在Windows上,它会将CRLF对转换成'\n' ,这对性能有一定的影响。 如果我正确地记得(我没有使用Java多年),Java期望更高级别的函数来处理这个问题,所以像readLine这样的函数会稍微复杂一些。 只是在这里猜测,但我怀疑,较高级别的附加逻辑在运行时比在较低级别的缓冲区预处理成本要低。 (如果你在Windows下进行testing,你可能会尝试在C ++中以二进制模式打开文件,当你使用>>时,程序的行为应该没有什么区别;任何额外的CR将被认为是空白的,你必须添加逻辑删除任何结尾的'\r'到你的代码。)

我怀疑,主要的区别是, java.io.BufferedReaderstd::ifstream执行更好,因为它缓冲,而ifsteam不。 当您调用readLine() ,BufferedReader会事先读取文件的大块,并将它们从RAM传递到您的程序,而std :: ifstream只会在您通过调用>> -运营商。

从硬盘顺序访问大量数据通常要比一次访问多个小块快得多。

比较公平的比较是将std :: ifstream与无缓冲的java.io.FileReader进行比较。

我不是C ++的专家,但至less有以下几点影响性能:

  1. 操作系统级caching的文件
  2. 对于Java,您使用的是缓冲读取器,缓冲区大小默认为页面或其他东西。 我不知道如何C ++stream这样做。
  3. 由于文件太大,JIT可能会被踢入,而且它可能会编译Java字节码,比如果你不对C ++编译器进行任何优化。

由于I / O成本是这里的主要成本,所以我认为1和2是主要原因。

我也会尝试使用mmap而不是标准的文件读/写。 这应该让你的操作系统处理读写,而你的应用程序只关心数据。

C ++不可能比Java更快,但是有时需要非常有才华的人做很多工作。 但我认为这不应该太难,因为这是一个简单的任务。

Windows的mmap在文件映射 ( MSDN )中进行了描述。