#AB01. 输入输出加速

输入输出加速

当前没有测试数据。

为什么需要输入输出加速?

默认情况下,C++ 的 cin 和 cout 的速度一般。问题主要有三个:

  1. 与 C 语言 I/O 的同步
    默认情况下,C++ 的 cincout 与 C 语言的 scanfprintf 是同步的。这让我们可以混合使用这两种 I/O 方式,但这种同步机制会带来显著的性能开销,因为它需要在两种不同的I/O库之间进行协调。

  2. cin 与 cout的绑定
    默认情况下,cin 与 cout 是绑定的。每次在执行输入操作之前,程序会自动刷新(flush)输出缓冲区。这样做是为了确保在请求用户输入之前,所有之前的输出都已经被显示在屏幕上。但在大量输入时,频繁刷新缓冲区会大大降低效率。

  3. endl的使用
    endl 不仅仅是一个换行符,它还会强制刷新输出缓冲区。在需要输出很多行时,每次都使用 endl 会导致频繁刷新缓冲区,从而严重影响性能。

如何输入输出加速?

在仅使用 C++ 风格输入输出(cin/cout)的前提下,我们一般会使用下面两行代码,并且,在输出时仅使用 '\n' 来进行换行,而不是 endl。

ios::sync_with_stdio(false);
cin.tie(0);// 或者 cin.tie(nullptr)

代码解释

  • ios::sync_with_stdio(false); 这条语句的作用是解除 C++ 和 C 的 I/O 同步。一旦关闭同步,C++ 的输入输出流就可以使用自己独立的缓冲区,从而省去了与 C 标准库同步所需的时间,运行效率得到大幅提升。需要注意的是,一旦使用了这行代码,就不能在程序中混用 cin/cout 和 scanf/printf,否则可能会导致输入输出顺序混乱。
  • cin.tie(0); 这条语句解除了cincout之间的绑定。tie的作用是确保在一个流上进行操作前,会先刷新其绑定的另一个流。默认cin绑定了cout,将其参数设为0或nullptr后,cin就不会再强制刷新cout了。 很多同学喜欢再写一个cout.tie(0);,但实际上这没有意义,因为cout 没有绑定其他流。使用 '\n' 替代
  • endl endl 会刷新缓冲区,而 '\n' 只是个换行符。程序结束时会自动刷新缓冲区,所以使用 '\n' 可以避免多次刷新缓冲区。

注意事项!

文件输入输出

不要手动关闭文件输入输出,即不要进行 fclose 等操作。程序结束时会自动刷新缓冲区并关闭你打开的文件。如果你提前手动关闭了并且在那之前没有刷新缓冲区,那你的输出就不会进入文件了。

交互题的强制刷新缓冲区

在交互题中,你的程序需要和评测程序进行交互通信。在这种情况下,你必须确保你的输出被立即发送给评测程序,而不是停留在缓冲区里。由于我们已经通过 cin.tie(0) 解除了绑定,cout 不会自动刷新。因此,在输出查询后,你需要使用下面的代码手动强制刷新缓冲区:

cout.flush();

调试出问题了?

输入输出加速后,你在本地调试时可能会觉得出 bug 了。实际上是因为解除了 cin 和 cout 的绑定,cout 不再自动刷新,导致在你输入所有数据之前,可能看不到任何程序输出,这让你没法实时监控程序。

一般我们有下面几种解决方法:

  1. 临时注释加速代码,但最后别忘了取消注释。
  2. 使用 cerr 进行调试输出 cerr 是一个标准错误流,它是非缓冲的。发送到 cerr 的输出会立即被显示出来,而不会被暂存在缓冲区。因此,你可以用 cerr 来输出调试信息,它不会受到I/O加速的影响。 一般情况下, cerr 的输出会被 OJ 和比赛的评测忽略,不会影响评测结果。但为了以防万一,还是建议你最后注释掉 cerr语句再提交。(不然你可能误以为 cerr 的输出是常规的输出,导致错误。)
  3. 在调试点手动刷新 cout,即在调试输出后执行 cout.flush(); 语句手动刷新缓冲区。