如何让IOStream执行得更好?

大多math习C的C ++用户更喜欢使用printf / scanf系列函数,即使他们使用C ++进行编码。

虽然我承认我发现接口方式更好(特别是类似POSIX的格式和本地化),但似乎压倒性的关注是性能。

看看这个问题:

我怎样才能加快逐行阅读的文件

看来最好的答案是使用fscanf ,并且C ++ ifstream速度一直要慢2-3倍。

我认为如果我们可以编译一个“提示”库来改善IOStreams的性能,那么效果会不错。

要点考虑

  • 缓冲( rdbuf()->pubsetbuf(buffer, size)
  • 同步( std::ios_base::sync_with_stdio
  • 区域设置处理(我们可以使用一个裁减的语言环境,或完全删除?)

当然,其他的方法是受欢迎的。

注意:Dietmar Kuhl提到了一个“新”实现,但是我无法find关于它的许多细节。 以前的参考文献似乎是死链接。

这是我迄今为止收集到的:

缓冲

如果默认缓冲区非常小,增加缓冲区大小肯定可以提高性能:

  • 它减less了硬盘命中的数量
  • 它减less了系统调用的次数

缓冲区可以通过访问底层streambuf实现来设置。

 char Buffer[N]; std::ifstream file("file.txt"); file.rdbuf()->pubsetbuf(Buffer, N); // the pointer reader by rdbuf is guaranteed // to be non-null after successful constructor 

警告:@iavr:根据cppreference最好在打开文件之前调用pubsetbuf 各种标准库实现方式有不同的行为。

区域设置处理:

区域设置可以执行字符转换,过滤和涉及数字或date的更聪明的技巧。 他们经历了一个复杂的dynamic调度和虚拟呼叫系统,所以删除它们可以帮助减less罚款。

默认的C语言环境意味着不执行任何转换以及跨机器统一。 这是一个很好的默认使用。

同步:

使用这个工具我看不到任何性能改进。

可以使用sync_with_stdio静态函数访问全局设置( std::ios_base静态成员)。

测量:

玩这个,我玩了一个简单的程序,编译使用gcc 3.4.2在SUSE 10p3与-O2

C:7.76532e + 06
C ++:1.0874e + 07

对于默认代码,这代表着大约20%的放缓。 实际上,篡改缓冲区(C或C ++)或同步参数(C ++)没有产生任何改进。

其他人的结果:

@Iffy在g ++ 4.7.2-2ubuntu1,-O3,虚拟化的Ubuntu 11.10,3.5.0-25-generic,x86_64,足够的ram / cpu,196MB的几个“find / >> largefile.txt”运行

C:634572 C ++:473222

C ++ 速度提高了25%

@Matteo意大利g ++ 4.4.5,-O3,Ubuntu Linux 10.10 x86_64随机180 MB文件

C:910390
C ++:776016

C ++ 快17%

在G ++上的@Bogatyr i686-apple-darwin10-g ++ – 4.2.1(GCC)4.2.1(Apple Inc. build 5664),mac mini,4GB RAM,空闲,除了这个testing有一个168MB数据文件

C:4.34151e + 06
C ++:9.14476e + 06

C ++ 慢了111%

@Asu on clang ++ 3.8.0-2ubuntu4,Kubuntu 16.04 Linux 4.8-rc3,8GB内存,i5 Haswell,Crucial SSD,88MB数据文件(tar.xz压缩文件)

C:270895 C ++:162799

C ++ 速度提高了66%

所以答案是:这是一个执行问题的质量,真的取决于平台:/

对于那些对基准testing感兴趣的代码,

 #include <fstream> #include <iostream> #include <iomanip> #include <cmath> #include <cstdio> #include <sys/time.h> template <typename Func> double benchmark(Func f, size_t iterations) { f(); timeval a, b; gettimeofday(&a, 0); for (; iterations --> 0;) { f(); } gettimeofday(&b, 0); return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) - (a.tv_sec * (unsigned int)1e6 + a.tv_usec); } struct CRead { CRead(char const* filename): _filename(filename) {} void operator()() { FILE* file = fopen(_filename, "r"); int count = 0; while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; } fclose(file); } char const* _filename; char _buffer[1024]; }; struct CppRead { CppRead(char const* filename): _filename(filename), _buffer() {} enum { BufferSize = 16184 }; void operator()() { std::ifstream file(_filename, std::ifstream::in); // comment to remove extended buffer file.rdbuf()->pubsetbuf(_buffer, BufferSize); int count = 0; std::string s; while ( file >> s ) { ++count; } } char const* _filename; char _buffer[BufferSize]; }; int main(int argc, char* argv[]) { size_t iterations = 1; if (argc > 1) { iterations = atoi(argv[1]); } char const* oldLocale = setlocale(LC_ALL,"C"); if (strcmp(oldLocale, "C") != 0) { std::cout << "Replaced old locale '" << oldLocale << "' by 'C'\n"; } char const* filename = "largefile.txt"; CRead cread(filename); CppRead cppread(filename); // comment to use the default setting bool oldSyncSetting = std::ios_base::sync_with_stdio(false); double ctime = benchmark(cread, iterations); double cpptime = benchmark(cppread, iterations); // comment if oldSyncSetting's declaration is commented std::ios_base::sync_with_stdio(oldSyncSetting); std::cout << "C : " << ctime << "\n" "C++: " << cpptime << "\n"; return 0; } 

另外两个改进:

发行std::cin.tie(nullptr); 在大量input/输出之前。

引用http://en.cppreference.com/w/cpp/io/cin

一旦构build了std :: cin,std :: cin.tie()就返回&std :: cout,同样,std :: wcin.tie()返回&std :: wcout。 这意味着任何格式化的std :: cininput操作都会强制调用std :: cout.flush(),如果任何字符等待输出。

你可以通过从std::cout解开std::cin来避免冲洗缓冲区。 这与多个对std::cinstd::cout混合调用有关。 请注意,调用std::cin.tie(std::nullptr); 使得程序不适合用户交互式运行,因为输出可能会延迟。

相关基准:

文件test1.cpp

 #include <iostream> using namespace std; int main() { ios_base::sync_with_stdio(false); int i; while(cin >> i) cout << i << '\n'; } 

文件test2.cpp

 #include <iostream> using namespace std; int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int i; while(cin >> i) cout << i << '\n'; cout.flush(); } 

两者都由g++ -O2 -std=c++11编译g++ -O2 -std=c++11 。 编译器版本: g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4 (是的,我知道,很老)。

基准testing结果:

 work@mg-K54C ~ $ time ./test1 < test.in > test1.in real 0m3.140s user 0m0.581s sys 0m2.560s work@mg-K54C ~ $ time ./test2 < test.in > test2.in real 0m0.234s user 0m0.234s sys 0m0.000s 

test.in包含1179648行,每行仅包含一个5 ,这是2.4 MB,非常抱歉不要在这里发布)。

我记得解决一个algorithm的任务,在线法官拒绝我的程序没有cin.tie(nullptr)但接受它与cin.tie(nullptr)printf / scanf而不是cin / cout

使用'\n'而不是std::endl

引用http://en.cppreference.com/w/cpp/io/manip/endl

在输出序列os中插入一个换行符,并通过调用os.put(os.widen('\ n'))和os.flush()来刷新它。

您可以通过打印'\n'来代替endl来避免冲洗缓冲器。

相关基准:

文件test1.cpp

 #include <iostream> using namespace std; int main() { ios_base::sync_with_stdio(false); for(int i = 0; i < 1179648; ++i) cout << i << endl; } 

文件test2.cpp

 #include <iostream> using namespace std; int main() { ios_base::sync_with_stdio(false); for(int i = 0; i < 1179648; ++i) cout << i << '\n'; } 

两者都如上编译。

基准testing结果:

 work@mg-K54C ~ $ time ./test1 > test1.in real 0m2.946s user 0m0.404s sys 0m2.543s work@mg-K54C ~ $ time ./test2 > test2.in real 0m0.156s user 0m0.135s sys 0m0.020s 

有趣的是你说C程序员在编写C ++时更喜欢printf,因为我看到很多C代码,而不是使用coutiostream来写输出。

通过直接使用filebuf可以获得更好的性能(Scott Meyers在Effective STL中提到过),但直接使用filebuf的文档相对较less,大多数开发人员更喜欢std::getline ,这在大多数情况下都比较简单。

关于语言环境,如果创build方面,通常只需创build一个语言环境一次就可以获得更好的性能,并保存它,然后将其填充到您使用的每个stream中。

最近我在这里看到了另外一个话题,所以这很接近重复。