如何让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::cin
和std::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代码,而不是使用cout
和iostream
来写输出。
通过直接使用filebuf
可以获得更好的性能(Scott Meyers在Effective STL中提到过),但直接使用filebuf的文档相对较less,大多数开发人员更喜欢std::getline
,这在大多数情况下都比较简单。
关于语言环境,如果创build方面,通常只需创build一个语言环境一次就可以获得更好的性能,并保存它,然后将其填充到您使用的每个stream中。
最近我在这里看到了另外一个话题,所以这很接近重复。