读入、输出优化
在默认情况下,std::cin/std::cout 是极为迟缓的读入/输出方式,而 scanf/printf 比 std::cin/std::cout 快得多。
注意
cin/cout 与 scanf/printf 的实际速度差会随编译器和操作系统的不同发生一定的改变。如果想要进行详细对比,请以实际测试结果为准。
下文将详细介绍读入输出的优化方法。
关闭同步/解除绑定
std::ios::sync_with_stdio(false)
这个函数是一个「是否兼容 stdio」的开关,C++ 为了兼容 C,保证程序在使用了 printf 和 std::cout 的时候不发生混乱,将输出流绑到了一起。同步的输出流是线程安全的。
这其实是 C++ 为了兼容而采取的保守措施,也是使 cin/cout 速度较慢的主要原因。我们可以在进行 IO 操作之前将 stdio 解除绑定,但是在这样做之后要注意不能同时使用 std::cin 和 scanf,也不能同时使用 std::cout 和 printf,但是可以同时使用 std::cin 和 printf,也可以同时使用 scanf 和 std::cout。
tie
tie 是将两个 stream 绑定的函数,空参数的话返回当前的输出流指针。
在默认的情况下 std::cin.tie() 绑定的是 &std::cout,每次进行格式化输入的时候都要调用 std::cout.flush() 清空输出缓冲区,这样会增加 IO 负担。可以通过 std::cin.tie(nullptr) 来解除绑定,进一步加快执行效率。
但需要注意的是,在解除了 std::cin 和 std::cout 的绑定后,程序中必须手动 flush 才能确保每次 std::cout 展现的内容可以在 std::cin 前出现。这是因为 std::cout 被 buffer 为默认设置。例如:
| C++ | |
|---|---|
1 2 3 4 5 6 | |
代码实现
| C++ | |
|---|---|
1 2 | |
读入优化
scanf 和 printf 依然有优化的空间,这就是本章所介绍的内容——读入和输出优化。
- 注意,本页面中介绍的读入和输出优化均针对整型数据,若要支持其他类型的数据(如浮点数),可自行按照本页面介绍的优化原理来编写代码。
原理
众所周知,getchar 是用来读入 1 byte 的数据并将其转换为 char 类型的函数,且速度很快,故可以用「读入字符——转换为整型」来代替缓慢的读入。
每个整数由两部分组成——符号和数字。
整数的 '+' 通常是省略的,且不会对后面数字所代表的值产生影响,而 '-' 不可省略,因此要进行判定。
10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入不应存在于整数中的字符(通常为空格)时,就可以判定已经读入结束。
C 和 C++ 语言分别在 ctype.h 和 cctype 头文件中,提供了函数 isdigit, 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true,否则返回 false。对应的,在下面的代码中,可以使用 isdigit(ch) 代替 ch >= '0' && ch <= '9',也可以使用 !isdigit(ch) 代替 ch <'0' || ch> '9'。
代码实现
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
- 举例
读入 num 可写为 num=read();。
输出优化
原理
同样是众所周知,putchar 是用来输出单个字符的函数。
因此将数字的每一位转化为字符输出以加速。
要注意的是,负号要单独判断输出,并且每次 %(mod)取出的是数字末位,因此要倒序输出。
代码实现
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 | |
但是递归实现常数是较大的,我们可以写一个栈来实现这个过程。
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 | |
- 举例
输出 num 可写为 write(num);。
更快的读入/输出优化
通过 fread 或者 mmap 可以实现更快的读入。
fread 能将需要的文件部分读入内存缓冲区。mmap 则会调度内核级函数,将文件一次性地映射到内存中,类似于可以指针引用的内存区域。所以在日常程序读写时,只需要重复读取部分文件可以使用 fread,因为如果用 mmap 反复读取一小块文件,做一次性内存映射并且内核处理 page fault 的花费会远比使用 fread 的内核级函数调度大。
同时 fread 和 mmap 由于是整段整段读取、写入,所以比 getchar()/putchar() 要快的多。并且 mmap 确保了进程间自动共享,存储区如果可以也会与内核缓存分享信息,确保了更少的拷贝操作。
fread 类似于参数为 "%s" 的 scanf,不过它更为快速,而且可以一次性读入若干个字符(包括空格换行等制表符),如果缓存区足够大,甚至可以一次性读入整个文件。
对于输出,我们还有对应的 fwrite 函数。
| C++ | |
|---|---|
1 2 3 4 | |
使用示例:fread(Buf, 1, SIZE, stdin),表示从 stdin 文件流中读入 SIZE 个大小为 1 byte 的数据块到 Buf 中。
读入之后的使用就跟普通的读入优化相似了,只需要重定义一下 getchar。它原来是从文件中读入一个 char,现在变成从 Buf 中读入一个 char,也就是头指针向后移动一位。
| C++ | |
|---|---|
1 2 3 4 5 | |
fwrite 也是类似的,先放入一个 OutBuf[MAXSIZE] 中,最后通过 fwrite 一次性将 OutBuf 输出。
参考代码:
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
mmap 是 linux 系统调用,可以将文件一次性地映射到内存中。在一些场景下有更优的速度。
注意 mmap 不能在 Windows 环境下使用(例如 CodeForces 的 tester),同时也不建议在正式赛场上使用,可以在卡常时使用。在使用前要引入 fcntl.h,unistd.h,sys/stat.h 与 sys/mman.h。
读入示例:首先要获取文件描述符 fd,然后通过 fstat 获取文件信息以得到文件大小,此后通过 char *pc = (char *) mmap(NULL, state.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 将指针 *pc 指向我们的文件。可以直接用 *pc ++ 替代 getchar()。
当我们要提交不使用文件操作的题目时,可以将 fd 设为 0,表示从 stdin 读入。但是,对 stdin 使用 mmap 是极其危险的行为,同时不能在终端输入,我们不建议您这么做。
参考代码
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
输入输出的缓冲
printf 和 scanf 是有缓冲区的。这也就是为什么,如果输入函数紧跟在输出函数之后/输出函数紧跟在输入函数之后可能导致错误。
刷新输出缓冲区的条件
- 程序结束;
- 关闭文件;
printf输出\r或者\n到终端的时候(注:如果是输出到文件,则不会刷新缓冲区);- 手动
fflush(); - 缓冲区满自动刷新;
cout输出endl;- 手动
cout.flush()。
使输入输出优化更为通用
如果你的程序使用多个类型的变量,那么可能需要写多个输入输出优化的函数。下面给出的代码使用 C++ 中的 template 实现了对于所有整数类型的输入输出优化。
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
如果要分别输入 int 类型的变量 a,long long 类型的变量 b 和 __int128 类型的变量 c,那么可以写成:
| C++ | |
|---|---|
1 2 3 | |
示例代码
| C++ | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
注意事项:
- 关闭调试开关时使用
fread(),fwrite(),退出时自动析构执行fwrite()。开启调试开关时使用getchar(),putchar(),便于调试。 - 若要进行文件读写,请在所有读写进行之前加入
freopen()。
例题:洛谷 P10815【模板】快速读入
读入 \(n\) 个范围在 \([-n, n]\) 的整数,求和并输出。其中 \(n \leq 10^8\)。数据保证对于序列的任何前缀,这个前缀的和在 \(32\) 位有符号整形的存储范围内。
参考
cin.tie 与 sync_with_stdio 加速输入输出 - 码农场
'Re: mmap/mlock performance versus read' - MARC
本页面的全部内容在 小熊老师 - 莆田青少年编程俱乐部 0594codes.cn 协议之条款下提供,附加条款亦可能应用