首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何让IOStream表现得更好?

如何让IOStream表现得更好?
EN

Stack Overflow用户
提问于 2011-03-02 18:39:19
回答 3查看 29.8K关注 0票数 63

大多数学习了C的C++用户更喜欢使用printf / scanf系列函数,即使是在用C++编码时也是如此。

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

看一下这个问题:

How can I speed up line by line reading of a file

似乎最好的答案是使用fscanf,而且C++ ifstream的速度总是慢2-3倍。

我认为如果我们能编译一个“小贴士”的存储库来提高IOStreams的性能,什么能行,什么不行,那就太好了。

需要考虑的几点

  • buffering (rdbuf()->pubsetbuf(buffer, size))
  • synchronization (std::ios_base::sync_with_stdio)
  • locale处理(我们是否可以使用精简的区域设置,或者将其完全删除?)

当然,也欢迎其他方法。

注意: Dietmar Kuhl提到了一个“新的”实现,但我找不到关于它的许多细节。之前的引用似乎都是死链接。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-03-02 21:52:02

这是我到目前为止收集到的:

缓冲

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

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

的次数

可以通过访问底层的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提供的警告:根据,最好在打开文件之前调用pubsetbuf。另外,不同的标准库实现具有不同的行为。

区域设置处理:

Locale可以执行字符转换、过滤以及涉及数字或日期的更巧妙的技巧。它们经历了一个复杂的动态调度和虚拟调用系统,因此删除它们可以帮助减少惩罚。

默认的C语言环境不会执行任何转换,并且在不同的机器上是一致的。这是一个很好的默认使用方法。

Synchronization:

我看不到使用这个工具有任何性能上的提升。

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

Measurements:

为了解决这个问题,我尝试了一个简单的程序,它是在SuSE10p3和-O2上使用gcc 3.4.2编译的。

C: 7.76532e+06

C++:1.0874e+07

这代表了大约20%的减速...用于默认代码。实际上,篡改缓冲区(在C或C++中)或同步参数(C++)并没有产生任何改进。

其他人的结果:

@Irfy on g++ 4.7.2-2ubuntu1,-O3,virtualized Ubuntu11.10,3.5.0-25-generic,x86_64,足够的内存/cpu,196MB的几个"find / >> largefile.txt“运行

C: 634572 C++:473222

C++ 速度提高25%

在g++ 4.4.5,-O3,Ubuntu Linux 10.10 x86_64上的

@Matteo Italia,随机180MB文件

C: 910390

C++:776016

C++ 速度提高17%

g++ i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (苹果公司版本5664)上的

@Bogatyr,mac mini,4 g内存,空闲,除了这个使用168MB数据文件的测试

C: 4.34151e+06

C++:9.14476e+06

C++ 速度降低111%

@Asu on clang++ 3.8.0-2ubuntu4,Kubuntu16.04 Linux4.8-RC3,8 8GB,i5 Haswell,关键固态硬盘,88MB数据文件(tar.xz归档)

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;
}
票数 49
EN

Stack Overflow用户

发布于 2016-02-11 21:29:05

另外两个改进:

在繁重的输入/输出之前发出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';
}

文件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.4MB,所以很抱歉没有在这里发布它。)

我记得解决了一个算法任务,在没有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';
}

两者都按照上面的方式编译。

基准测试结果:

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
票数 18
EN

Stack Overflow用户

发布于 2011-03-02 19:29:53

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

用户通常可以通过直接使用filebuf获得更好的性能(Scott Meyer在Effective中提到了这一点),但是关于直接使用filebuf的文档相对较少,而且大多数开发人员更喜欢std::getline,因为它在大多数情况下更简单。

关于locale,如果您创建facet,那么通过使用所有facet创建一次locale,保存它,并将其注入到您使用的每个流中,通常会获得更好的性能。

我最近确实在这里看到了另一个关于这个的话题,所以这几乎是一个复制品。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5166263

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档