使用 std::setvbuf 加速输入输出

发布时间 2023-10-30 00:36:49作者: caijianhong

市面上的快读和快写,大致过程是手动扩大缓冲区,并手动将数字转化为字符,使用 fread / fwrite 进行最终缓冲区的输入和输出。

考虑阅读 std::setvbuf 的文档(link),发现这东西可以设置自己的缓冲区,为自己所用,同时可以设置大小。由此,我们不难写出如下的神秘代码:

#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
constexpr size_t mybufsize = 1 << 20;
char myinbuf[mybufsize], myoutbuf[mybufsize];
int main() {
  setvbuf(stdin, myinbuf, _IOFBF, mybufsize);
  setvbuf(stdout, myoutbuf, _IOFBF, mybufsize);
  unsigned long long ans = 0, x;
  for (int i = 0; i < 3e6; i++) {
    scanf("%llu", &x);
    ans ^= x;
  }
  printf("%llu\n", ans);
  return 0;
}

这段代码首先篡改了 stdiostdout 的缓冲区并使其大小扩大到 1 << 20。然后进行正常的 scanf / printf 输入输出。在 loj.ac/p/7 上跑到 900 ms。与平常手写的快读速度慢一点点。

发现 scanf 解析 const char* fmt 的过程本身也有常数,考虑更换为关闭与 stdio 同步的 cin / cout,可以跑到 700 ms:


#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
typedef long long LL;
constexpr size_t mybufsize = 1 << 21;
char myinbuf[mybufsize], myoutbuf[mybufsize];
int main() {
  setvbuf(stdin, myinbuf, _IOFBF, mybufsize);
  setvbuf(stdout, myoutbuf, _IOFBF, mybufsize);
  cin.tie(nullptr) -> sync_with_stdio(false);
  unsigned long long ans = 0, x;
  for (int i = 0; i < 3e6; i++) {
    cin >> x;
    ans ^= x;
  }
  cout << ans << endl;
  return 0;
}

运行时间 788 ms。于是你获得一个和手写快读速度差不多的快读。

请注意,你可能需要先进行 freopen 再进行 setvbuf,因为

此函数仅可在已将 stream 关联到打开的文件后,但要在任何其他操作(除了对 std::setbuf/std::setvbuf 的失败调用)前使用。