C++性能真的不如C吗?

ACM比赛整理

共 7068字,需浏览 15分钟

 ·

2022-06-20 00:00


你好,我是雨乐!

最近在知乎上看了篇帖子,题目是为什么C++没有C语言快,如下图:

恰好之前研究过这块,所以借助本文,分析下这个问题(无意于语言之争,单纯是从技术角度😁)。

众所周知,C++兼容了C的所有功能,显然从所有角度去对比分析是不现实的,所以本文从我们常用的输入输出即标准流(iostream和stdio)的角度来分析讲解。

示例

为了更加直观地来对比分析,写了个示例,通过scanf和cin读文件,然后分析两种方式的性能高低,代码如下:

#include <chrono>
#include <functional>
#include <iostream>
#include <fstream>
const int num=1000000;

void time_report(const std::function<void()> &f1, const std::function<void()> &f2) {
   auto start = std::chrono::high_resolution_clock::now();
   f1();
   auto end = std::chrono::high_resolution_clock::now();
   std::cout << "cin cost " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;

   start = std::chrono::high_resolution_clock::now();
   f2();
   end = std::chrono::high_resolution_clock::now();
   std::cout << "scanf cost " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
}


void write() {
  std::ofstream file("./data");
  for(int i = 0; i < num; ++i) {
    file << rand() <<' ';
    if((i + 1) % 20 == 0) {
      file << std::endl;
    }
  }
}
int main() {
  write(); 
  
  time_report([](){
  freopen("./data","r",stdin);
  int n = 0;
  for(int i=0; i<num; i++) {
      std::cin >> n;
  }
}, [](){
  freopen("./data","r",stdin);
  int n = 0;
  for(int i = 0; i < num; ++i) {
     scanf("%d", &n);
  }
});

return 0;
}

编译,运行之后,输出如下:

cin cost 686ms
scanf cost 189ms

从上述输出来看,cin的耗时是scanf的3倍多,果真如此么?

sync_with_stdio

C++性能真的差C这么多吗?直接颠覆了对C++的认知,即使性能真的低,也得知道为什么低吧,于是开始研究,发现C++为了兼容C,在C标准流(stdio)和C++标准流(iostrem)保持同步,这样就可以混合使用C和C++风格的I/O,且能保证得到合理和预期的结果,而正是这个同步导致C++在cin性能上有损失。如果禁用同步,则允许C++流拥有自己的独立缓冲区,这样性能就会提升很多。

那么是否可以禁用该同步功能呢?

C++提供了一个函数std::ios::sync_with_stdio,声明如下:

static bool sync_with_stdio(bool __sync = true);

如果参数为false,则代表禁用此同步。从上面声明可以看出,默认情况下 __sync = true也就是说禁用同步,而如果__sync为false的话,则会有如下操作:

bool
  ios_base::sync_with_stdio(bool __sync) {
    bool __ret = ios_base::Init::_S_synced_with_stdio;
    
    if (!__sync && __ret) {
       // ...
       cout.rdbuf(&buf_cout);
       cin.rdbuf(&buf_cin);
       cerr.rdbuf(&buf_cerr);
       clog.rdbuf(&buf_cerr);
       // ...
    }
  return __ret;
}

从上述代码,进一步验证了我们上面的说法,如果禁用了同步功能,则C++流使用自己的缓冲区buf_cin(此处以cin为例),几种buffer的定义如下:

 typedef char fake_filebuf[sizeof(stdio_filebuf<char>)]
  __attribute__ ((aligned(__alignof__(stdio_filebuf<char>))));
  fake_filebuf buf_cout;
  fake_filebuf buf_cin;
  fake_filebuf buf_cerr;

好了,截止到现在,我们已经搞清楚了为什么C++流性能要慢于C,为了验证是否真的是因为使用了同步功能而导致的性能差异,使用std::ios::sync_with_stdio(false)关闭同步,代码示例如下:

#include <chrono>

#include <functional>
#include <iostream>
#include <fstream>
const int num=1000000;

void time_report(const std::function<void()> &f1, const std::function<void()> &f2) {
   auto start = std::chrono::high_resolution_clock::now();
   f1();
   auto end = std::chrono::high_resolution_clock::now();
   std::cout << "cin cost " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;

   start = std::chrono::high_resolution_clock::now();
   f2();
   end = std::chrono::high_resolution_clock::now();
   std::cout << "scanf cost " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
}


void write() {
  std::ofstream file("./data");
  for(int i = 0; i < num; ++i) {
    file << rand() <<' ';
    if((i + 1) % 20 == 0) {
      file << std::endl;
    }
  }
}
int main() {
  std::ios::sync_with_stdio(false);
  write(); 
  
  time_report([](){
  freopen("./data","r",stdin);
  int n = 0;
  for(int i=0; i<num; i++) {
      std::cin >> n;
  }
}, [](){
  freopen("./data","r",stdin);
  int n = 0;
  for(int i = 0; i < num; ++i) {
     scanf("%d", &n);
  }
});

return 0;
}

编译,运行后,输出如下:

cin cost 178ms
scanf cost 189ms

可以看出禁用同步后,二者的性能基本一致。

既然禁用同步后,C++流的性能与C基本一致,那么是否直接禁用呢?答案是依赖于具体的使用场景。

1、同步的C++流是线程安全的,也就说来自不同线程的输出可能会交错,但数据不会产生竞争,而如果禁用同步,则可能出现意想不到的结果。

2、如果禁用了同步功能,输入输出顺序可能会得不到我们想要的结果。

#include <stdio.h>
#include <iostream>

int  main() {
  std::cout << "a ";
  printf("b ");
  std::cout << "c ";
  return 0;
}

上述代码执行后,输出a b c ,符合我们的预期。

如果加上禁用同步代码,如下:

#include <stdio.h>
#include <iostream>

int  main() {
std::ios_base::sync_with_stdio(false);
std::cout << "a ";
printf("b ");
std::cout << "c ";

return 0;
}

编译、运行之后,结果为a c b,与我们期望的不一致。

结语

如果使用C编程,那么使用C stdio,而如果使用C++编程,则建议使用C++ I/O。如果在某些特殊场景下,需要混合使用,那么强烈建议不要禁用同步,否则会得到意想不到的结果。

好了,今天的文章就到这,我们下期见!

浏览 10
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报