大多数学习C的C ++用户更喜欢使用printf
/ scanf
系列函数,即使他们使用C ++进行编码也是如此。
虽然我承认我发现接口方式更好(特别是类似POSIX的格式和本地化),但似乎压倒性的是性能。
看看这个问题:
似乎最好的答案是使用fscanf
并且C ++ ifstream
的速度始终慢2-3倍。
我认为如果我们可以编译一个“提示”存储库来提高IOStream的性能,什么可行,什么不可行,那将会很棒。
要考虑的要点
rdbuf()->pubsetbuf(buffer, size)
)std::ios_base::sync_with_stdio
)当然,欢迎其他方法。
注意:提到了Dietmar Kuhl的“新”实现,但我无法找到有关它的许多细节。以前的引用似乎是死链接。
答案 0 :(得分:47)
以下是我迄今收集的内容:
<强>缓冲强>:
如果默认情况下缓冲区非常小,增加缓冲区大小肯定会提高性能:
可以通过访问基础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
。否则,各种标准库实现会有不同的行为。
区域设置处理:
Locale可以执行字符转换,过滤以及涉及数字或日期的更聪明的技巧。他们经历了动态调度和虚拟呼叫的复杂系统,因此删除它们可以帮助减少惩罚。
默认C
语言环境意味着不执行任何转换以及跨机器统一。这是一个很好的默认使用。
<强>同步强>
使用此工具我无法看到任何性能提升。
可以使用std::ios_base
静态函数访问全局设置(sync_with_stdio
的静态成员)。
<强>测量强>
玩这个,我玩了一个简单的程序,使用gcc 3.4.2
在SUSE 10p3上使用-O2
进行编译。
C:7.76532e + 06
C ++:1.0874e + 07
对于默认代码,代表约20%
...的减速。实际上,篡改缓冲区(使用C或C ++)或同步参数(C ++)并没有带来任何改进。
其他人的结果:
@Irfy on g ++ 4.7.2-2ubuntu1,-O3,虚拟化Ubuntu 11.10,3.5.0-25-generic,x86_64,足够的ram / cpu,196MB的几个“find /&gt;&gt; largefile.txt”运行
C:634572 C ++:473222
C ++ 快25%
@Matteo Italia on g ++ 4.4.5,-O3,Ubuntu Linux 10.10 x86_64,随机180 MB文件
C:910390
C ++:776016
C ++ 快17%
@Bogatyr on g ++ i686-apple-darwin10-g ++ - 4.2.1(GCC)4.2.1(Apple Inc. build 5664),mac mini,4GB ram,空闲除了这个带有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 ram,i5 Haswell,Crucial SSD,88MB datafile(tar.xz archive)
C:270895 C ++:162799
C ++ 快66%
所以答案是:这是一个实施质量问题,实际上取决于平台:/
对于那些对基准测试感兴趣的人来说,这里有完整的代码:
#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;
}
答案 1 :(得分:15)
另外两项改进:
std::cin.tie(nullptr);
。引用http://en.cppreference.com/w/cpp/io/cin:
一旦构造了std :: cin,std :: cin.tie()返回&amp; std :: cout,同样,std :: wcin.tie()返回&amp; std :: wcout。这意味着std :: cin上的任何格式化输入操作都会强制调用std :: cout.flush(),如果有任何字符等待输出。
您可以通过从std::cin
解除std::cout
来清除缓冲区。这与多个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++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
(是的,我知道,很老)。
基准测试结果:
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,很抱歉不在此处发布。)。
我记得解决了一个算法任务,其中在线法官在没有cin.tie(nullptr)
的情况下一直拒绝我的程序,但是正在接受cin.tie(nullptr)
或printf
/ scanf
而不是{{1} }} / cin
。
cout
代替'\n'
。引用http://en.cppreference.com/w/cpp/io/manip/endl:
在输出序列os中插入换行符并将其刷新,就像调用os.put(os.widen('\ n'))后跟os.flush()一样。
您可以通过打印std::endl
而不是'\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';
}
答案 2 :(得分:1)
有趣的是你说C程序员在编写C ++时更喜欢printf,因为除了使用cout
和iostream
来编写输出之外,我看到很多代码都是C语言。
直接使用filebuf
可以获得更好的性能(Scott Meyers在Effective STL中提到了这一点),但使用filebuf direct的文档相对较少,大多数开发人员更喜欢std::getline
,这更简单时间。
关于区域设置,如果您创建构面,通常会使用所有构面创建一次区域设置,保存它并将其添加到您使用的每个流中,从而获得更好的性能。
我最近在这里看到了另一个关于此问题的话题,所以这几乎是重复的。