如何让IOStream表现更好?

Mat*_* M. 62 c++ optimization iostream c++-faq c++-standard-library

大多数学习C的C++用户都喜欢使用printf/ scanf系列函数,即使他们使用C++进行编码也是如此.

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

看看这个问题:

如何加快逐行读取文件的速度

似乎最好的答案是使用fscanf并且C++的ifstream速度始终慢2-3倍.

我认为如果我们能够编译一个"技巧"存储库以提高IOStream的性能,哪些有效,哪些无效,我会觉得很棒.

要考虑的要点

  • 缓冲(rdbuf()->pubsetbuf(buffer, size))
  • 同步(std::ios_base::sync_with_stdio)
  • 区域设置处理(我们可以使用修剪后的区域设置,还是完全删除它?)

当然,欢迎其他方法.

注意:提到了Dietmar Kuhl的"新"实现,但我无法找到有关它的许多细节.以前的引用似乎是死链接.

Mat*_* M. 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
Run Code Online (Sandbox Code Playgroud)

警告@iavr提供:根据cppreference,最好pubsetbuf在打开文件之前调用.否则,各种标准库实现具有不同的行为.

区域处理:

Locale可以执行字符转换,过滤以及涉及数字或日期的更聪明的技巧.他们经历了动态调度和虚拟呼叫的复杂系统,因此删除它们可以帮助减少惩罚命中.

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

同步:

使用此工具我无法看到任何性能提升.

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

测量:

玩这个,我玩了一个简单的程序,使用gcc 3.4.2SUSE 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: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;
}
Run Code Online (Sandbox Code Playgroud)

  • @Bogatyr`gettimeofday`,如果有什么*比*time更精确*.此外,这**是现实世界案例的一个很好的近似:读取数据.毕竟,我们不想测量其他东西,只需要读取数据.所以这个基准很好.将两个代码放在同一个可执行文件中也完全没问题.只需确保运行足够的基准测试以抵消预热减速(或者在开始时运行一次,Mathieu会这样做).该基准测试**优于您建议的"改进". (3认同)

gaa*_*kam 15

另外两项改进:

std::cin.tie(nullptr);重输入/输出之前的问题.

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

构造std :: cin之后,std :: cin.tie()返回&std :: cout,同样,std :: wcin.tie()返回&std :: wcout.这意味着std :: cin上的任何格式化输入操作都会强制调用std :: cout.flush(),如果有任何字符待处理输出的话.

您可避免解开刷新缓冲区std::cinstd::cout.这与多个混合调用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';
}
Run Code Online (Sandbox Code Playgroud)

档案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();
}
Run Code Online (Sandbox Code Playgroud)

两者都编译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
Run Code Online (Sandbox Code Playgroud)

(test.in由1179648行组成,每行只包含一个5.它是2.4 MB,很抱歉不在这里发布.).

我记得解决了一个算法任务,在那里,在线法官一直拒绝我的程序,cin.tie(nullptr)但是却用/ cin.tie(nullptr)或代替/ 接受它.printfscanfcincout

'\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;
}
Run Code Online (Sandbox Code Playgroud)

档案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';
}
Run Code Online (Sandbox Code Playgroud)

两者都如上编译.

基准测试结果:

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
Run Code Online (Sandbox Code Playgroud)

  • 啊,是的,`endl`情况通常是afficionados所熟知的,但是很多教程默认使用它(为什么????)它会定期绊倒初学者/中级程序员.至于"领带":我今天正在学习一些东西!我知道提示用户会强制冲洗,但不知道它是如何被控制的. (3认同)