zl程序教程

您现在的位置是:首页 >  Java

当前栏目

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

2023-02-19 12:19:23 时间

最近在知乎上看了篇帖子,题目是为什么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。如果在某些特殊场景下,需要混合使用,那么强烈建议不要禁用同步,否则会得到意想不到的结果。