为什么这个C代码比这个C++代码快?获取文件中的最大行
Why is this C code faster than this C++ code ? getting biggest line in file
我有两个版本的程序,它们基本上做同样的事情,在一个文件中获取最大长度的行,我有一个大约 8000 行的文件,我的 C 代码比我在 C++ 中的代码更原始(当然!(。C 程序大约需要 2 秒才能运行,而 C++ 中的程序需要 10 秒才能运行(我在这两种情况下测试的相同文件(。但是为什么?我预计它需要相同的时间或更多一点,但不会慢 8 秒!
我的 C 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if _DEBUG
#define DEBUG_PATH "../Debug/"
#else
#define DEBUG_PATH ""
#endif
const char FILE_NAME[] = DEBUG_PATH "data.noun";
int main()
{
int sPos = 0;
int maxCount = 0;
int cPos = 0;
int ch;
FILE *in_file;
in_file = fopen(FILE_NAME, "r");
if (in_file == NULL)
{
printf("Cannot open %sn", FILE_NAME);
exit(8);
}
while (1)
{
ch = fgetc(in_file);
if(ch == 0x0A || ch == EOF) // n or r or rn or end of file
{
if ((cPos - sPos) > maxCount)
maxCount = (cPos - sPos);
if(ch == EOF)
break;
sPos = cPos;
}
else
cPos++;
}
fclose(in_file);
printf("Max line length: %in", maxCount);
getch();
return (0);
}
我在C++的代码:
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string>
using namespace std;
#ifdef _DEBUG
#define FILE_PATH "../Debug/data.noun"
#else
#define FILE_PATH "data.noun"
#endif
int main()
{
string fileName = FILE_PATH;
string s = "";
ifstream file;
int size = 0;
file.open(fileName.c_str());
if(!file)
{
printf("could not open file!");
return 0;
}
while(getline(file, s) )
size = (s.length() > size) ? s.length() : size;
file.close();
printf("biggest line in file: %i", size);
getchar();
return 0;
}
我的猜测是,这是您使用的编译器选项、编译器本身或文件系统的问题。 我刚刚编译了两个版本(进行了优化(,并针对 92,000 行文本文件运行它们:
c++ version: 113 ms
c version: 179 ms
我怀疑C++版本更快的原因是因为 fgetc 很可能更慢。 fgetc
确实使用缓冲 I/O,但它正在进行函数调用来检索每个字符。 我之前已经测试过它,fgetc
不如在一次调用中调用读取整行那么快(例如,与fgets
相比(。
在一些评论中,我回应了人们的回答,即问题可能是您的C++版本所做的额外复制,它将行复制到字符串中的内存中。但我想测试一下。
首先,我实现了 fgetc 和 getline 版本并对其进行了计时。我确认在调试模式下,getline 版本较慢,大约 130 μs,而 fgetc 版本为 60 μs。这并不奇怪,因为传统观点认为iostreams比使用stdio慢。然而,在过去,根据我的经验,iostreams 从优化中获得显着的速度。当我比较我的释放模式时间时,这一点得到了证实:使用 getline 大约 20 μs,使用 fgetc 大约 48 μs。
至少在发布模式下,将 getline 与 iostreams 一起使用比 fgetc 更快这一事实与复制所有数据必须比不复制数据慢的推理背道而驰,所以我不确定所有优化都能够避免什么,我真的没有想找到任何解释,但了解正在优化的内容会很有趣。 编辑:当我使用分析器查看程序时,如何比较性能并不明显,因为不同的方法看起来彼此如此不同
Anwyay 我想看看我是否可以通过避免在 fstream 对象上使用 get()
方法进行复制来获得更快的版本,而只是完全按照 C 版本正在做的事情去做。当我这样做时,我很惊讶地发现在调试和发布中使用 fstream::get()
都比 fgetc 和 getline 方法慢得多;调试时约为 230 μs,发布时约为 80 μs。
为了缩小速度减慢的范围,我继续并做了另一个版本,这次使用附加到 fstream 对象的stream_buf,并snextc()
方法。这个版本是迄今为止最快的;调试时为 25 μs,发布时为 6 μs。
我猜使fstream::get()
方法如此慢的是它为每个调用构造一个哨兵对象。虽然我没有测试过这个,但我看不出除了从stream_buf中获取下一个角色之外,get()
还有很多作用,除了这些哨兵对象。
无论如何,这个故事的寓意是,如果你想要快速的io,你最好使用高级iostream函数而不是stdio,并且对于真正快速的io访问底层stream_buf。编辑:实际上这个道德可能只适用于MSVC,请参阅底部的更新以获取来自不同工具链的结果。
供参考:
我使用了 VS2010 和 boost 1.47 的计时。我构建了 32 位二进制文件(似乎是 boost chrono 所必需的,因为它似乎找不到该库的 64 位版本(。我没有调整编译选项,但它们可能不完全是标准的,因为我是在我保留的临时项目中这样做的。
我测试的文件是 1.1 MB 20,000 行纯文本版本的 Oeuvres Complètes de Frédéric Bastiat,由 Project Gutenberg 的 Frédéric Bastiat 撰写,http://www.gutenberg.org/ebooks/35390
释放模式时间
fgetc time is: 48150 microseconds
snextc time is: 6019 microseconds
get time is: 79600 microseconds
getline time is: 19881 microseconds
调试模式时间:
fgetc time is: 59593 microseconds
snextc time is: 24915 microseconds
get time is: 228643 microseconds
getline time is: 130807 microseconds
这是我fgetc()
版本:
{
auto begin = boost::chrono::high_resolution_clock::now();
FILE *cin = fopen("D:/bames/automata/pg35390.txt","rb");
assert(cin);
unsigned maxLength = 0;
unsigned i = 0;
int ch;
while(1) {
ch = fgetc(cin);
if(ch == 0x0A || ch == EOF) {
maxLength = std::max(i,maxLength);
i = 0;
if(ch==EOF)
break;
} else {
++i;
}
}
fclose(cin);
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << 'n';
std::cout << "fgetc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << 'n';
}
这是我getline()
版本:
{
auto begin = boost::chrono::high_resolution_clock::now();
std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
unsigned maxLength = 0;
std::string line;
while(std::getline(fin,line)) {
maxLength = std::max(line.size(),maxLength);
}
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << 'n';
std::cout << "getline time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << 'n';
}
fstream::get()
版本
{
auto begin = boost::chrono::high_resolution_clock::now();
std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
unsigned maxLength = 0;
unsigned i = 0;
while(1) {
int ch = fin.get();
if(fin.good() && ch == 0x0A || fin.eof()) {
maxLength = std::max(i,maxLength);
i = 0;
if(fin.eof())
break;
} else {
++i;
}
}
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << 'n';
std::cout << "get time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << 'n';
}
和snextc()
版本
{
auto begin = boost::chrono::high_resolution_clock::now();
std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
std::filebuf &buf = *fin.rdbuf();
unsigned maxLength = 0;
unsigned i = 0;
while(1) {
int ch = buf.snextc();
if(ch == 0x0A || ch == std::char_traits<char>::eof()) {
maxLength = std::max(i,maxLength);
i = 0;
if(ch == std::char_traits<char>::eof())
break;
} else {
++i;
}
}
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << 'n';
std::cout << "snextc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << 'n';
}
更新:
我在 OS X 上使用 libc++ 上的 clang (trunk( 重新运行了测试。基于 iostream 的实现的结果保持相对相同(打开优化(; fstream::get()
比std::getline()
慢得多 比filebuf::snextc()
慢得多.但fgetc()
的性能相对于getline()
实现有所提高,并且变得更快。也许这是因为getline()
完成的复制成为此工具链的问题,而 MSVC 则没有?也许Microsoft的 fgetc(( 的 CRT 实现很糟糕还是什么?
无论如何,这是时间(我使用了一个更大的文件,5.3 MB(:
使用 -Os
fgetc time is: 39004 microseconds
snextc time is: 19374 microseconds
get time is: 145233 microseconds
getline time is: 67316 microseconds
使用 -O0
fgetc time is: 44061 microseconds
snextc time is: 92894 microseconds
get time is: 184967 microseconds
getline time is: 209529 microseconds
-O2
fgetc time is: 39356 microseconds
snextc time is: 21324 microseconds
get time is: 149048 microseconds
getline time is: 63983 microseconds
-O3
fgetc time is: 37527 microseconds
snextc time is: 22863 microseconds
get time is: 145176 microseconds
getline time is: 67899 microseconds
C++ 版本不断分配和解除分配 std::string 的实例。内存分配是一项成本高昂的操作。除此之外,还执行构造函数/析构函数。
然而,C 版本使用常量内存,并且只是必要的:读取单个字符,将行长计数器设置为新值(如果更高(,则为每个换行符,仅此而已。
你不是在比较苹果和苹果。C 程序不会将数据从缓冲区复制到程序FILE*
内存中。它还对原始文件进行操作。
您的C++程序需要多次遍历每个字符串的长度 - 一次在流代码中知道何时终止它返回给您的字符串,一次在std::string
的构造函数中,一次在代码对。s.length()
的调用中
您可以提高 C 程序的性能,例如,如果您可以使用 getc_unlocked
来。但最大的胜利来自不必复制数据。
:根据bames53的评论进行编辑
2 秒只需 8.000 行?我不知道你的台词有多长,但很有可能你做错了什么。
这个微不足道的Python程序几乎可以立即执行,从Project Gutenberg下载El Quijote(40006行,2.2MB(:
import sys
print max(len(s) for s in sys.stdin)
时间:
~/test$ time python maxlen.py < pg996.txt
76
real 0m0.034s
user 0m0.020s
sys 0m0.010s
您可以通过缓冲输入而不是逐个字符读取 char 来改进 C 代码。
关于为什么C++比C慢,应该与构建字符串对象然后调用length方法有关。在 C 中,你只是边走边数字符。
我尝试针对 40K 行C++源代码编译和运行您的程序,它们都在大约 25 毫秒左右完成。我只能得出结论,您的输入文件有很长的行,每行可能 10K-100K 个字符。在这种情况下,C 版本不会因长行长度而产生任何负面影响,而 C++ 版本必须不断增加字符串的大小并将旧数据复制到新缓冲区中。如果它必须增加足够多的大小,则可以解释过度的性能差异。
这里的关键是这两个程序不做同样的事情,所以你不能真正比较它们的结果。如果您能够提供输入文件,我们也许能够提供其他详细信息。
您可能可以使用tellg
和ignore
在C++中更快地执行此操作。
C++程序构建行的字符串对象,而 C 程序只读取字符并查看字符。
编辑:
感谢您的赞成票,但经过讨论,我现在认为这个答案是错误的。这是一个合理的初步猜测,但在这种情况下,似乎不同的(并且非常慢的(执行时间是由其他原因引起的。
我对理论家没问题。但是,让我们来谈谈经验。
我生成了一个包含 1300 万行文本文件的文件。
~$ for i in {0..1000}; do cat /etc/* | strings; done &> huge.txt
编辑为从stdin
读取的原始代码(不应对性能产生太大影响(在将近2分钟内完成了。
C++代码:
#include <iostream>
#include <stdio.h>
using namespace std;
int main(void)
{
string s = "";
int size = 0;
while (cin) {
getline(cin, s);
size = (s.length() > size) ? s.length() : size;
}
printf("Biggest line in file: %in", size);
return 0;
}
C++时间:
~$ time ./cplusplus < huge.txt
real 1m53.122s
user 1m29.254s
sys 0m0.544s
"C"版本:
#include <stdio.h>
int main(void)
{
char *line = NULL;
int read, max = 0, len = 0;
while ((read = getline(&line, &len, stdin)) != -1)
if (max < read)
max = read -1;
printf("Biggest line in file %dn", max);
return 0;
}
C 性能:
~$ time ./ansic < huge.txt
real 0m4.015s
user 0m3.432s
sys 0m0.328s
做你自己的数学...
- 在C++代码中包含opencv时,使用ctypes创建.so文件
- 此代码编译良好,但文件未创建?请指出错误
- 找不到Linux Visual Studio代码C++文件
- C++(.cpp文件和.h文件)拆分代码并添加一个函数,提取 - 这很容易吗?
- Visual Studio代码文件未输出
- 是否可以使用 GCC 编译具有特定编译器标志的代码文件的一部分?
- 如何从ifstream加载LLVM比特代码文件
- 在同一代码文件中包括 Directx 9 和 10
- 如果我只想要架构良好的工作区,但不一定是分开的编译,如何在C++中组织头/代码文件
- 使用 Eclipse 运行.exe C 代码文件
- 是否可以稍后在代码(C++ I/O 文件)中使用输出文件作为输入文件
- 匿名命名空间-在头文件和代码文件中都有意义
- 代码文件外的代码文档
- 在静态库中硬编码的代码文件和头路径
- 更快地解析代码文件
- 为什么有些代码文件使用 close() 而不包含 unistd.h
- 在代码(.c文件)我如何找到linux发行版名称版本
- 在一个代码文件中使用头文件,反之亦然
- SonarQube未分析带有警告的C++代码文件不在项目目录下
- Qt 创建器代码文件重构