如何让 IOStream 性能更好?
大多数学习 C 的 C++ 用户更喜欢使用 printf
/scanf
系列函数,即使他们在使用 C++ 编码.
虽然我承认我发现界面更好(尤其是类似 POSIX 的格式和本地化),但似乎压倒性的关注是性能.
看看这个问题:
<块引用>如何加快逐行读取文件的速度
似乎最好的答案是使用 fscanf
并且 C++ ifstream
始终慢 2-3 倍.
我认为如果我们可以编译一个技巧"存储库来提高 IOStreams 性能,哪些有效,哪些无效.
要考虑的要点
- 缓冲 (
rdbuf()->pubsetbuf(buffer, size)
) - 同步(
std::ios_base::sync_with_stdio
) - 语言环境处理(我们可以使用精简的语言环境,还是完全删除它?)
当然,欢迎使用其他方法.
注意:提到了 Dietmar Kuhl 的新"实现,但我无法找到有关它的许多细节.以前的参考文献似乎是死链接.
解决方案这是我目前收集到的:
缓冲:
如果默认缓冲区很小,增加缓冲区大小肯定可以提高性能:
- 它减少了硬盘命中次数
- 减少系统调用次数
可以通过访问底层的streambuf
实现来设置缓冲区.
char Buffer[N];std::ifstream 文件("file.txt");file.rdbuf()->pubsetbuf(Buffer, N);//保证 rdbuf 的指针读取器//构造函数成功后非空
@iavr 提供的警告:根据 cppreference最好在打开文件之前调用 pubsetbuf
.否则,各种标准库实现具有不同的行为.
区域设置处理:
Locale 可以执行字符转换、过滤以及涉及数字或日期的更聪明的技巧.它们经历了一个复杂的动态调度和虚拟调用系统,因此删除它们有助于减少惩罚.
默认的 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++) 并没有产生任何改进.
其他人的结果:
<块引用>@Irfy on g++ 4.7.2-2ubuntu1,-O3,虚拟化 Ubuntu 11.10,3.5.0-25-generic,x86_64,足够的 ram/cpu,196MB 的几个find/>> largefile.txt"运行>
C:634572C++:473222
C++ 快 25%
<块引用>@Matteo Italia 在 g++ 4.4.5、-O3、Ubuntu Linux 10.10 x86_64 上使用一个随机的 180 MB 文件
电话: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 内存,空闲,除了这个测试有 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 数据文件(tar.xz 存档)
C : 270895C++:162799
C++ 快 66%
所以答案是:这是一个实施质量问题,实际上取决于平台:/
对于那些对基准测试感兴趣的人,这里有完整的代码:
#include #include #include #include <cmath>#include #include 模板 双基准(Func f,size_t 迭代){F();时间值 a, b;gettimeofday(&a, 0);for (; 迭代 --> 0;){F();}gettimeofday(&b, 0);返回 (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -(a.tv_sec * (unsigned int)1e6 + a.tv_usec);}结构 CRead{CRead(char const* 文件名): _filename(filename) {}无效运算符()(){FILE* file = fopen(_filename, "r");整数计数 = 0;while ( fscanf(file,"%s", _buffer) == 1 ) { ++count;}fclose(文件);}字符常量* _文件名;字符_缓冲区[1024];};结构 CppRead{CppRead(char const* filename): _filename(filename), _buffer() {}枚举 { BufferSize = 16184 };无效运算符()(){std::ifstream 文件(_filename, std::ifstream::in);//删除扩展缓冲区的注释file.rdbuf()->pubsetbuf(_buffer, BufferSize);整数计数 = 0;std::string s;而(文件>> s){ ++计数;}}字符常量* _文件名;char _buffer[BufferSize];};int main(int argc, char* argv[]){size_t 迭代次数 = 1;if (argc > 1) { 迭代 = atoi(argv[1]);}char const* oldLocale = setlocale(LC_ALL,"C");如果(strcmp(oldLocale,C")!= 0){std::cout <<替换了旧的语言环境"<<oldLocale<<"' by 'C'
";}char const* 文件名 = "大文件.txt";CRead 读取(文件名);CppRead cppread(文件名);//注释以使用默认设置bool oldSyncSetting = std::ios_base::sync_with_stdio(false);双 ctime = 基准(创建,迭代);双 cpptime = 基准(cppread,迭代);//如果注释了 oldSyncSetting 的声明,则注释std::ios_base::sync_with_stdio(oldSyncSetting);std::cout <<C:"<<时间<<"
"C++:"<<cpptime<<"
";返回0;}
Most C++ users that learned C prefer to use the printf
/ scanf
family of functions even when they're coding in C++.
Although I admit that I find the interface way better (especially POSIX-like format and localization), it seems that an overwhelming concern is performance.
Taking at look at this question:
How can I speed up line by line reading of a file
It seems that the best answer is to use fscanf
and that the C++ ifstream
is consistently 2-3 times slower.
I thought it would be great if we could compile a repository of "tips" to improve IOStreams performance, what works, what does not.
Points to consider
- buffering (
rdbuf()->pubsetbuf(buffer, size)
) - synchronization (
std::ios_base::sync_with_stdio
) - locale handling (Could we use a trimmed-down locale, or remove it altogether ?)
Of course, other approaches are welcome.
Note: a "new" implementation, by Dietmar Kuhl, was mentioned, but I was unable to locate many details about it. Previous references seem to be dead links.
解决方案Here is what I have gathered so far:
Buffering:
If by default the buffer is very small, increasing the buffer size can definitely improve the performance:
- it reduces the number of HDD hits
- it reduces the number of system calls
Buffer can be set by accessing the underlying streambuf
implementation.
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
Warning courtesy of @iavr: according to cppreference it is best to call pubsetbuf
before opening the file. Various standard library implementations otherwise have different behaviors.
Locale Handling:
Locale can perform character conversion, filtering, and more clever tricks where numbers or dates are involved. They go through a complex system of dynamic dispatch and virtual calls, so removing them can help trimming down the penalty hit.
The default C
locale is meant not to perform any conversion as well as being uniform across machines. It's a good default to use.
Synchronization:
I could not see any performance improvement using this facility.
One can access a global setting (static member of std::ios_base
) using the sync_with_stdio
static function.
Measurements:
Playing with this, I have toyed with a simple program, compiled using gcc 3.4.2
on SUSE 10p3 with -O2
.
C : 7.76532e+06
C++: 1.0874e+07
Which represents a slowdown of about 20%
... for the default code. Indeed tampering with the buffer (in either C or C++) or the synchronization parameters (C++) did not yield any improvement.
Results by others:
@Irfy on g++ 4.7.2-2ubuntu1, -O3, virtualized Ubuntu 11.10, 3.5.0-25-generic, x86_64, enough ram/cpu, 196MB of several "find / >> largefile.txt" runs
C : 634572 C++: 473222
C++ 25% faster
@Matteo Italia on g++ 4.4.5, -O3, Ubuntu Linux 10.10 x86_64 with a random 180 MB file
C : 910390
C++: 776016
C++ 17% faster
@Bogatyr on g++ i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664), mac mini, 4GB ram, idle except for this test with a 168MB datafile
C : 4.34151e+06
C++: 9.14476e+06
C++ 111% slower
@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% faster
So the answer is: it's a quality of implementation issue, and really depends on the platform :/
The code in full here for those interested in benchmarking:
#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'
";
}
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 << "
"
"C++: " << cpptime << "
";
return 0;
}
相关文章