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

55

我有一个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.10f s\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++的性能?


13
先运行C++版本,然后是Java版本,最后再运行一遍C++版本。这可以避免系统缓存的影响。(不过,即使考虑到磁盘I/O,69秒也相当长。) - nneonneo
4
另外,请问你是在开启优化选项(-O)的情况下编译了吗? - nneonneo
8
C++ STL 的实现通常在没有像内联这样的编译器优化的情况下表现很差。你永远不应该关闭优化并查看 C++ 程序的性能;这是毫无意义的。 - heinrichj
4
一个细节:C++不会自动将变量count初始化为零。 - Thomas Padron-McCarthy
2
我并不太惊讶Java比C++ iostreams快一点。iostreams的设计受到面向对象和使用虚函数的影响。与Java虚拟机不同,C++无法将虚拟调用转换为内联调用。通过使用仅读取数据而不是尝试支持虚拟数据源和虚拟格式操作的C stdio函数,您可以获得更好的性能。 - Zan Lynx
显示剩余14条评论
5个回答

65

你没有在比较相同的东西。Java程序按行读取,根据换行符,而C++程序则读取以空格为分隔符的“单词”,这需要额外的工作。

尝试使用istream::getline

稍后

您还可以尝试执行基本的读取操作,以读取字节数组并扫描其中的换行符。

再晚点

在我的旧Linux笔记本上,jdk1.7.0_21和不要告诉我它已经过时的4.3.3与C++ getline相比,大约需要相同的时间。(我们已经确定读取单词较慢。)-O0和-O2之间没有太大的区别,这让我感到不惊讶,考虑到循环中代码的简单性。

最后注

正如我所建议的,使用LEN = 1MB的fin.read(buffer,LEN),并使用memchr扫描'\n'会使速度提高约20%,这使得C(现在已经没有任何C ++了)比Java更快。


14
为什么计数要使用双精度? - laune
1
我使用getline(fin, word),结果是43秒,但Java仍然比它更快。 - dodolong
1
在Linux上,文本文件的预处理是无操作的,我期望大多数实现都会认识到这一点,并简单地忽略文本/二进制选项。在所有其他系统上,将读取内容转换为文本格式所需的预处理将影响C++的性能。 - James Kanze
1
关于您最后的注释:这也会使处理过程显着复杂化。如果将std::filebuf的缓冲区大小设置为1MB,会产生什么影响? - James Kanze
2
+1 最终转向C语言以获得更好的性能。 - 2rs2ts
显示剩余9条评论

8

编程语言处理I/O的方式有许多显著的差异,这些差异可能会影响程序的运行结果。

也许最重要的问题是:文本文件中的数据如何编码。如果是单字节字符(ISO 8859-1UTF-8),那么Java在处理之前必须将其转换为UTF-16;根据区域设置,C++也可能会进行一些额外的转换或检查。

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

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

最后,一个最显著的区别:C++区分文本文件和二进制文件。您已经以文本模式打开了文件;这意味着它将在最低级别上进行“预处理”,甚至在提取运算符看到它之前。这取决于平台:对于Unix平台,“预处理”无操作;在Windows上,它将把CRLF对转换为'\n',这会对性能产生明显影响。如果我记得正确(我已经好几年没有用Java了),Java期望高级函数来处理此问题,因此像readLine之类的函数会稍微复杂一些。只是猜测,在更高级别的附加逻辑成本比较底层缓冲预处理的运行时成本要少。 (如果您在Windows下测试,可以尝试以C++的二进制模式打开文件。当使用>>时,这不应该影响程序的行为;任何额外的CR都将被视为空格。使用getline时,您需要添加逻辑以从代码中删除任何结尾的'\r'。)


5
我认为主要的区别在于java.io.BufferedReaderstd::ifstream表现更好,因为它进行了缓冲。BufferedReader预先读取文件的大块内容,并在您调用readLine()时从RAM中将其传递给程序;而std::ifstream只有在您通过调用>>运算符时才会读取一些字节。

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

一个更公平的比较应该是将std::ifstream与未缓冲的java.io.FileReader进行比较。


6
std::ifstream 转发到 std::filebuf,通常具有缓冲功能。(标准只需要一个字符的缓冲区,但大多数实现会使用类似8K的东西。) - James Kanze

4
我不是C++专家,但至少以下几点会影响性能:
  1. 文件的操作系统级缓存
  2. 对于Java,您正在使用缓冲读取器,并且缓冲区大小默认为页面或某些内容。我不确定C++流如何处理此操作。
  3. 由于文件非常大,JIT可能会被触发,并且它可能会编译Java字节码,而不是为C++编译器打开任何优化。
由于I/O成本是主要成本,因此我猜1和2是主要原因。

根据他的描述,我确信不同的缓冲策略起着重要作用。 - James Kanze
是的,考虑到该程序可能是I/O绑定的,并且JIT编译器将处理中心循环,因此人们不会期望C++有任何固有优势。 - Christian

2
我还建议尝试使用mmap而不是标准文件读写。这样可以让操作系统处理读写,而应用程序只关注数据。
C++永远比Java更快的情况是不存在的,但有时需要非常有才华的人来完成很多工作。但我认为这个任务应该不难打败,因为它是一项直截了当的任务。
Windows中的mmap在File MappingMSDN)中有描述。

如果我们想要比较mmap实现,我们也应该在Java中使用FileChannel。 - digital_infinity

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接