我有一个2GB的文件(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
", 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 ++的性能?
运行C ++版本,然后运行Java版本,再运行C ++版本。这可能会捕获系统缓存。 (但是,即使占用磁盘I / O,69秒也很长)。
此外,您是否在启用或不启用优化(-O)的情况下编译?
你尝试过普通的C吗?尝试与典型的C进行比较并调整块和缓冲区大小,这将是有趣的。
@nneonneo我没有使用任何-O优化。如果我需要它,我应该使用什么级别?现在我想知道C ++流(fin)是否比Java BufferReader慢?
@ user3513917在没有内联等编译器优化的情况下使用时,C ++ STL实现通常表现不佳。你永远不应该关闭优化的C ++程序的性能;这没有意义。
您在Java代码中运行readline,在C ++代码中运行ifstream>>。当您运行>>时,您会以字符为基础读取数据,换句话说,您的磁盘头会像这样移动(读取1个字符)=>(输入word)=>重复。换句话说,读取"apple"这个词你的磁盘头移动5次携带1个字符,而在Java代码中你的磁盘头移动1次携带5个字符块(实际上)。这里的诀窍是,在I / O操作中花费的最多时间恰好是磁盘磁头移动。这可能不完全准确,但一般来说这是真的。
@ConstantineSamoilenko是的,这是我的C ++代码的瓶颈,我通过使用getline(fin,word)和使用-O2 optimation来改变它。现在C ++运行38s,Java运行32s。 Java仍然比C ++更快。我不知道C ++是否比Java更快。
@ConstantineSamoilenko这里涉及的磁头没有移动;数据被缓冲。 (检测单词结尾所需的测试可能比检测行结束所需的测试更复杂,但除此之外,>>和getline非常相似。两者都需要查看每个字符的读取。)
你阅读文件的方式是不现实的。以大块和时间读取文件。而且你不应该为控制台打印时间。
为什么你用双倍计数? 5e8在int的范围内很好。它需要更少的内存并且运行得更快
@自。他阅读文件的方式非常逼真; C ++ std::filebuf和Java的BufferedReader都应该使用针对给定平台优化的缓冲。如果你可以改进它,那么实现就有问题了。
@L?uV?nhPhc是的,这是一个错误,我应该使用int。
一个细节:C ++不会自动将变量计数初始化为零。
Java并不比C ++ iostream快一点,我也不会感到惊讶。 iostreams设计受到面向对象和使用虚函数的困扰。与Java虚拟机不同,C ++无法将虚拟调用转换为内联调用。通过使用仅读取数据的C stdio函数而不是尝试支持虚拟数据源和虚拟格式操作,可以获得更好的性能。
哦耶。理论上,如果您承诺二进制文件是自包含的,那么C ++编译器可以内联虚拟调用。如果使用GCC,请在编译时尝试-fwhole-program选项。
这个平台是什么?使用本机文件I / O API可能会大大提升性能
即使使用fopen和fscanf也会带来很大的性能提升。
在c ++中快速文本文件读取的可能重复
OT:为什么要写一个程序? wc是完成wc -w $ filename的工具。
你不是在比较同样的事情。 Java程序读取行,取决于换行符,而C ++程序读取空格分隔的"单词",这是一个额外的工作。
试试istream::getline。
后来
您也可以尝试执行基本读取操作来读取字节数组并对其进行扫描以获取换行符。
甚至更晚
在我的旧Linux笔记本上,与C ++ getline相比,jdk1.7.0_21和不告诉我它的旧版本4.3.3几乎同时进行。 (我们已经确定读取单词较慢。)-O0和-O2之间没有太大区别,考虑到循环中代码的简单性,这并不让我感到惊讶。
最后一点
正如我所说,fin.read(缓冲区,LEN)LEN = 1MB并使用memchr扫描' n'导致另一个速度提高约20%,这使得C(现在还没有任何C ++)比Java快。
为什么要使用double进行计数?
我使用getline(fin,word),结果是43s,Java仍然比它快。
@dodolong:DONT比较调试版本。使用优化。
在Linux上,文本文件的预处理是无操作的,我希望大多数实现都能识别这一点,并且只是忽略文本/二进制选项。在所有其他系统上,将读取的内容转换为文本格式所需的预处理将影响C ++的性能。
@dodolong:谁说C ++必须比Java更快? JIT将足够快,并且Java的底部也有C。
@laune没有人说过纯CPU性能(可能会有所不同 - 最终,Java和C ++都会被编译成机器代码,或多或少都是最佳的)。这里的问题是IO。
关于你的最后一点:它也使得处理变得更加复杂。如果将std::filebuf上的缓冲区大小设置为1MB,会有什么影响?
@James Kanze使用带有ifstream和getline的1MB缓冲区可将时间缩短10%。另外10%的"纯C"版本增益应该是因为避免了getline调用。
+1为了性能而最终转入C语言。
我肯定在C ++的Windows上使用ZwCreateFile和ZwReadFile会因为删除许多缓冲区副本而击败Java和C.
@JamesKanze:使用C ++ iostreams,性能限制器很少是I / O系统。方面逻辑很慢。至少在常见的实现上。
@BenVoigt如果没有正确实现,std::filebuf中codecvt facet的必要使用会大大减慢速度;它没有必要,但正如你所指出的,在许多常见的实现中,它确实如此。但是,如果数据尚未被系统缓存,则从磁盘获取8K缓冲区可能需要大约10ms。即使是std::filebuf的不良实现也不需要在10ms左右重新映射缓冲区。
@James:10ms寻求时间,当然。但操作系统使用预读缓存,因此每8KB块不需要支付10毫秒。更像是10ms寻道时间+每1MB块的10ms传输时间(假设碎片最小)。不幸的是,std::filebuf在流行的实现上通常需要超过20ms / MB。
如果为C版本设置1MB缓冲区,则应对Java版本(BufferedReader的构造函数参数)执行相同操作。
+1表示明确答案。 :)
在方式上有很多显着的差异
语言处理I / O,所有这些都可以通过单向方式发挥作用
或其他。
也许第一个(也是最重要的)问题是:如何?
文本文件中编码的数据。如果是单字节字符
(ISO 8859-1或UTF-8),然后Java必须将其转换为UTF-16
在处理之前;根据语言环境,C ++可能(或可能不)
还转换或做一些额外的检查。
正如已经指出的那样(至少部分地),在C ++中,>>使用
特定于语言环境的isspace,getline将简单地进行比较
'
',这可能更快。 (典型的实现
isspace将使用位图,这意味着额外的内存
访问每个角色。)
优化级别和特定库实现可能
也各不相同。在C ++中,一个库并不罕见
实施速度比另一个快2到3倍。
最后,最重要的区别是:C ++的区别
文本文件和二进制文件之间。你打开了文件
文字模式;这意味着它将被"预处理"
最低级别,甚至在提取操作员看到它之前。这个
取决于平台:对于Unix平台,"预处理"
是无操作的;在Windows上,它会将CRLF对转换为'
',
这将对性能产生一定的影响。如果我记得
Java期望正确(我已经多年没用过Java了)
更高级别的函数来处理这个,所以函数就像
readLine会稍微复杂一些。只是猜测
在这里,但我怀疑更高的额外逻辑
运行时的级别成本低于缓冲区预处理的级别
低等级。 (如果您在Windows下进行测试,可能会
尝试在C ++中以二进制模式打开文件。这个
应该对程序的行为没有任何影响
你用>>;任何额外的CR都将被视为空白区域。同
getline,您必须添加逻辑以删除任何尾随
'
'代码。)
我怀疑主要的区别是java.io.BufferedReader比std::ifstream表现更好,因为它缓冲,而ifsteam没有。 BufferedReader提前读取文件的大块并在调用readLine()时将它们从RAM传递给程序,而std :: ifstream一次只读取几个字节,当你通过调用>>来提示它时 - 运算符。
从硬盘顺序访问大量数据通常比一次访问一个小块快得多。
更公平的比较是将std :: ifstream与未缓冲的java.io.FileReader进行比较。
std::ifstream转发到std::filebuf,通常缓冲。 (标准只需要一个字符缓冲区,但大多数实现将使用类似8K的东西。)
我不是C ++专家,但你至少有以下几点影响性能:
文件的操作系统级缓存
对于Java,您使用的是缓冲读取器,缓冲区大小默认为页面或其他内容。我不确定C ++流是如何做到这一点的。
由于文件太大而JIT可能会被踢入,并且它可能比你没有为C ++编译器进行任何优化更好地编译Java字节代码。
由于I / O成本是这里的主要成本,我猜1和2是主要原因。
鉴于他的描述,我确信不同的缓冲策略起着重要作用。
是的,鉴于该程序可能是I / O绑定的,JIT编译器无论如何都会处理中央循环,人们不会期望C ++有任何固有的优势。
我也会尝试使用mmap而不是标准文件读/写。 这应该让您的操作系统处理读写,而您的应用程序只关心数据。
没有哪种情况下C ++不能比Java更快,但有时需要很多人才才能完成。 但我不认为这个应该太难以击败,因为这是一个简单的任务。
文件映射(MSDN)中描述了适用于Windows的mmap。
如果我们想要比较mmap实现,我们也应该在Java中使用FileChannel。