C++输入输出效率测试

前情提要:

默认情况下,C++的标准库(iostream,例如 std::cinstd::cout)与C的标准库(stdio,例如 scanfprintf)之间是同步的。这意味着它们共享相同的缓冲区,并且在每次I/O操作时都会进行刷新和同步,以确保它们之间的一致性。这种同步确保了你可以在程序中混合使用C和C++风格的I/O,而不会出现数据错乱或顺序错误的问题。

然而,这种同步会带来性能开销。当你知道在程序中不需要混合使用C和C++风格的I/O时,可以通过调用std::ios::sync_with_stdio(false) 来关闭它们之间的同步。这样做之后,C++的I/O操作会变得更快,因为它们不再需要在每次操作时与C的I/O函数保持同步。

在竞赛中,除非用快读之类的技巧,一般选手都会选择使用C标准输入输出,这篇博客是探究关闭同步流后的C++的流输入输出与C标准输入输出在效率上的优劣对比。需要注意的是,本测试将输入输出合并在一个程序中,以期得出一个综合成绩,如果有不妥之处,请诸位提出。

本篇测试全程在Linux下进行,以下是测试环境:

64cf2203cdbc34c0c388888cd5d0fa36.png

gcc版本采用arch滚动最新的13.2.1和较老的9.5.0进行测试

计时程序:time.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <chrono>
#include <bits/stdc++.h>
using namespace std;
int main() {
string st;
cin >> st;
string exe = "./" + st + " < ./input.in > ./output.out";
cout << exe << "\n";
auto start = chrono::high_resolution_clock::now();
system(exe.c_str());
auto end = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(end - start).count();
cout << "************************************\n";
cout << "Time: " << duration << "ms\n";
return 0;
}

一、正整数读入

数据规模:读入2000万 输出2000万 int正整数

采用两种不同版本的编译器,分别为gcc 13.2.1和gcc 9.5.0

数据生成程序

generate.cpp

1
2
3
4
5
6
7
8
9
10
#include <cstdio>
using namespace std;
int main()
{
freopen("input.in", "w", stdout);
for (int i = 1; i <= 20000000; i++)
printf("%d ", i);
printf("\n");
return 0;
}

1. 流输入输出 关闭同步流

Test1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int t;
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for (int i = 1; i <= 20000000; i++)
cin >> t;
for (int i = 1; i <= 20000000; i++)
cout << i << " ";
cout << "\n";
return 0;
}

编译命令:

1
g++ Test1.cpp -o2 -o Test1.exe

gcc 13.2.1

三次结果:

dc7a5863baba52289df0da853384a0db.png

平均结果:6525 ms

gcc 9.5.0:

三次结果:

b1232b0634c04b60dea4cfb35c909127.png

平均结果:6451 ms

2. C标准输入输出

Test2.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int t;
for (int i = 1; i <= 20000000; i++)
scanf("%d", &t);
for (int i = 1; i <= 20000000; i++)
printf("%d ", i);
printf("\n");
return 0;
}

编译命令:

1
g++ Test2.cpp -o2 -o Test2.exe

gcc 13.2.1:

三次结果:

030e9d59736555ad728aeb0177da2581.png

平均结果:7310 ms

gcc 9.5.0:

三次结果:

337a8058cbdf23d1b9e457f81eb5bd6d.png

平均结果:7483 ms

3. 流输入输出 不关闭同步流

Test3.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int t;
// ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for (int i = 1; i <= 20000000; i++)
cin >> t;
for (int i = 1; i <= 20000000; i++)
cout << i << " ";
cout << "\n";
return 0;
}

编译命令:

1
g++ Test3.cpp -o2 -o Test3.exe

gcc 13.2.1:

三次结果:

0abb95ab28357ee2a320dde419ba8706.png

平均结果:15463 ms

gcc 9.5.0:

三次结果:

b838bfcbd87c6b1f64862fd2d3b2dfdf.png

平均结果:15552 ms

4. 总结

C标准输入输出 C++流输入输出 C++流输入输出 关闭同步流
gcc 13.2.1 7310 ms 15463 ms 6525 ms
gcc 9.5.0 7483 ms 15552 ms 6451 ms

从表中不难看出,C++的流输入输出在关闭同步流后比原本提高了至少130%,比竞赛中常用的C标准输入输出也有至少10%的提升。

更值得注意的是gcc的各个版本似乎对于整数输入输出的影响并不显著

二、浮点数读入

数据规模:读入2000万 输出2000万 double双精度浮点数

采用两种不同版本的编译器,分别为gcc 13.2.1和gcc 9.5.0

数据生成程序

generate.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <cstdio>
#include <cstdlib>
#include <random>
#include <ctime>
using namespace std;
int main()
{
freopen("input.in", "w", stdout);
srand(time(NULL));
for (int i = 1; i <= 20000000; i++)
{
int t = rand() % 100;
double temp = t * 1.0 / 100;
printf("%.2lf ", temp + (double)i);
}
printf("\n");
return 0;
}

1. 流输入输出 关闭同步流

Test1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double t;
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for (int i = 1; i <= 20000000; i++)
cin >> t;
double temp = 0.04;
for (int i = 1; i <= 20000000; i++)
cout << (double)i + temp << " ";
cout << "\n";
return 0;
}

编译命令:

1
g++ Test1.cpp -o2 -o Test1.exe

gcc 13.2.1:

三次结果:

852158f5d76cae5a86818991b0456f4b.png

平均结果:29848 ms

gcc 9.5.0:

三次结果:

b010f5047e69a5ce8454cca5971e638a.png

平均结果:29026 ms

2. C标准输入输出

Test2.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double t;
for (int i = 1; i <= 20000000; i++)
scanf("%lf", &t);
double temp = 0.04;
for (int i = 1; i <= 20000000; i++)
printf("%.2lf ", (double)i + temp);
printf("\n");
return 0;
}

编译命令:

1
g++ Test2.cpp -o2 -o Test2.exe

gcc 13.2.1:

三次结果:

9d95f6080647c304141d8f3ad0abf886.png

平均结果:25484 ms

gcc 9.5.0:

三次结果:

f490005761f230994f3117ca3f261b12.png

平均结果:25846 ms

3. 流输入输出 不关闭同步流

Test3.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double t;
// ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for (int i = 1; i <= 20000000; i++)
cin >> t;
double temp = 0.04;
for (int i = 1; i <= 20000000; i++)
cout << (double)i + temp << " ";
cout << "\n";
return 0;
}

编译命令:

1
g++ Test3.cpp -o2 -o Test3.exe

gcc 13.2.1:

三次结果:

64c7fc2da9f745a9c941b53de200cc5f.png

平均结果:43383 ms

gcc 9.5.0:

三次结果:

b3567ea3b5090b1d186ea29e1e69d836.png

平均结果:43289 ms

4. 总结

C标准输入输出 C++流输入输出 C++流输入输出 关闭同步流
gcc 13.2.1 25484 ms 43383 ms 29848 ms
gcc 9.5.0 25846 ms 43289 ms 29026 ms

在浮点数的输入输出中,C++的流输入输出在关闭同步流的提升没有整数型那么恐怖,但是仍达到了至少40%;但是相对比C标准输入输出,却比起慢了相对15%。同样,gcc的各个版本似乎对于浮点数输入输出的影响并不显著。

三、总结

综合以上的数据,我们可以看出C++流输入输出关闭同步流之后,效率与C标准输入输出不相上下,与不关闭同步流相比均有显著提升。

具体在整数方面,关闭同步流的流输入输出比C标准输入输出稍快,而在浮点数方面则相反。

测试过程仍存在不严谨之处,例如输入输出分开测试、更老版本的gcc并未测试等,不过仍希望能成为各位的一个参考。