在C++程序中使用scanf()比使用cin更快吗?

151

我不知道这是否属实,但当我在一个问题提供网站上阅读FAQ时,我发现了一些引起我注意的内容:

检查您的输入/输出方式。 在C ++中,使用cin和cout太慢了。 使用它们,您将无法解决任何具有适量输入或输出的问题。 而应该使用printf和scanf来代替。

能否有人澄清一下这个问题? 在C ++程序中,使用 scanf()比使用 cin >> something 快吗? 如果是的话,在C ++程序中使用它是一个好习惯吗? 我认为那只是 C语言 特有的,虽然我正在学习C ++...


我很好奇他们遇到了什么问题,以至于认为这会成为一个问题。你有那个网站的链接吗? - Eclipse
17
我的猜测是:不好的程序员会把标准库的性能问题归咎于标准库本身,这有点像那些总是喊着“我发现了GCC的bug”的幽默者。 - John Kugelman
12
@eclipse:我为比赛做过的ACM问题涉及大量输入/输出,你的程序必须在60秒内解决问题...这在这里成为了一个真正的问题。 - mpen
23
话虽如此,如果您需要依赖scanf()来获得额外的性能提升,那么您处理问题的方式是错误的 :) - mpen
这是关于cin的一些有趣事情(滚动到讨论其在实现中减速的部分):http://unthought.net/c++/c_vs_c++.html - Johannes Schaub - litb
4
作为一项观察,我进行了尝试,并且在第二个问题(PRIME1)中使用了相同的算法,在两种情况下都进行了尝试:一次使用cin/cout,一次使用scanf/printf。第一种版本比第二种更快(但足够接近以至于统计上没有差异)。这是被标记为输入/输出密集型的问题之一,然而输入/输出的方法在统计学上没有任何区别。 - Eclipse
12个回答

246

这是一个简单情况的快速测试:一个从标准输入读取数字列表并对所有数字进行XOR的程序。

使用iostream版本:

#include <iostream>

int main(int argc, char **argv) {

  int parity = 0;
  int x;

  while (std::cin >> x)
    parity ^= x;
  std::cout << parity << std::endl;

  return 0;
}

scanf版本:

#include <stdio.h>

int main(int argc, char **argv) {

  int parity = 0;
  int x;

  while (1 == scanf("%d", &x))
    parity ^= x;
  printf("%d\n", parity);

  return 0;
}

结果

我使用第三方程序生成了一个包含33,280,276个随机数的文本文件。执行时间如下:

iostream version:  24.3 seconds
scanf version:      6.4 seconds

调整编译器的优化设置似乎并没有显著改变结果。

因此: 确实存在速度差异。


编辑: 用户clyfish在下面指出,速度差异主要是由于iostream I/O函数与C I/O函数维护同步。我们可以通过调用std::ios::sync_with_stdio(false);来关闭同步:

#include <iostream>

int main(int argc, char **argv) {

  int parity = 0;
  int x;

  std::ios::sync_with_stdio(false);

  while (std::cin >> x)
    parity ^= x;
  std::cout << parity << std::endl;

  return 0;
}

新的结果:

iostream version:                       21.9 seconds
scanf version:                           6.8 seconds
iostream with sync_with_stdio(false):    5.5 seconds

C++ iostream胜出!原来内部同步/刷新是导致iostream I/O通常变慢的原因。如果我们不混用stdio和iostream,可以关闭它,这样iostream是最快的。

代码:https://gist.github.com/3845568


8
我认为使用'endl'可能会减慢执行速度。 - Krishna Mohan
5
std::endl 的使用不在循环中。 - nibot
无论同步打开或关闭都没有区别。这要归咎于libc++。它只能提升libstdc++。 - iBug
你认为<cstdio>和<stdio.h>之间会有什么区别吗? - Chandrahas Aroori
1
当您在一个 scanf 调用中解析多个整数时,iostream 会失去优势。 - Maxim Egorushkin
然后在关闭同步之后,如果有人向您的程序添加了putchar(),会发生什么? - Blair Houghton

87

1
为了获得更好的性能,还可以使用 cin.tie(static_cast<ostream*>(0));。 - Mohamed ElNakeep

43

也许scanf比使用流略快一些。虽然流提供了很多类型安全性,并且不必在运行时解析格式字符串,但通常它有一个优点,即不需要过多的内存分配(这取决于您的编译器和运行时环境)。尽管如此,除非性能是您唯一的目标,并且您处于关键路径中,否则您应该真正青睐更安全(较慢)的方法。

这里有一篇非常美味的文章,由Herb Sutter撰写"The String Formatters of Manor Farm",详细介绍了字符串格式化程序(如sscanf和lexical_cast)的性能以及使其运行缓慢或快速的原因。这与C样式IO和C++样式之间的性能影响可能有类似之处。格式化程序的主要区别在于类型安全性和内存分配数量。


22

我刚刚花了一个晚上在UVa Online解决了一个问题(《因数分解器》,非常有趣的问题,请查看):

http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=35&page=show_problem&problem=1080

我的提交结果显示“超时”(TLE)。 在这些问题解决在线评测网站上,您有大约2-3秒的时间限制来处理可能达到数千个测试用例以评估您的解决方案。 对于像这样计算密集型的问题,每微秒都很重要。

我正在使用建议的算法(在该站点的讨论论坛中阅读),但仍然遇到TLE。

我只是将 “cin >> n >> m” 更改为 “scanf("%d %d", &n, &m)” 并将少量的 “couts” 更改为 “printfs”,我的TLE变成了“已接受”!

所以,是的,它可以有很大的影响,特别是当时间限制很短时。


6

完全同意。但是你需要知道,FastFormat只用于输出。它没有输入/读取功能。(至少目前还没有) - dcw
很遗憾,该链接似乎已经失效。这里有一个 Wayback Machine 的备份:http://web.archive.org/web/20081222164527/http://fastformat.org/ - nibot

5

一般情况下,在C ++中使用的语句cincout似乎比scanfprintf慢,但实际上它们更快!

原因在于:在C ++中,每当您使用cincout时,默认情况下会发生同步过程,以确保如果您在程序中同时使用scanfcin,它们将相互同步。这个同步过程需要时间。因此,cincout看起来比较慢。

然而,如果设置同步过程不发生,cinscanf快。

要跳过同步过程,请在程序的开头的main()中包含以下代码片段:

std::ios::sync_with_stdio(false);

欲了解更多信息,请访问此网站


2

有一些stdio实现(libio)将FILE*实现为C++ streambuf,将fprintf实现为运行时格式解析器。IOstreams不需要运行时格式解析,这些都在编译时完成。因此,如果后端共享,则可以合理地期望iostreams在运行时更快。


我不这么认为。我认为GNU的libc是纯C和汇编语言编写的。 - Chris Lutz
那并不能让它变得更好。 - Luka Govedič

1

是的,iostream 比 cstdio 慢。
如果你在开发 C++,最好不要使用 cstdio。
话虽如此,如果你不关心格式、类型安全等等,甚至有比 scanf 更快的方法来进行 I/O...

例如,这是一个从 STDIN 获取数字的自定义例程:

inline int get_number()
{
    int c;        
    int n = 0;

    while ((c = getchar_unlocked()) >= '0' && c <= '9')
    {
        // n = 10 * n + (c - '0');
        n = (n << 3) + ( n << 1 ) + c - '0';
    }
    return n;
}

1
getchar_unlocked() 是非标准的函数,只能在gcc中使用,而不能在Visual Studio中使用。 - Mohamed ElNakeep

0

当然,使用iostream而不是cstdio是荒谬的。至少在开发软件时(如果您已经使用c++而不是c,那么请充分利用它的优势,而不仅仅遭受它的缺点)。

但是在在线评测中,您不是在开发软件,而是创建一个程序,该程序应该能够在3秒内完成Microsoft软件需要60秒才能完成的任务!

因此,在这种情况下,黄金法则如下(当然,如果您使用Java会更麻烦):

  • 使用c++并充分利用其功能(以及沉重/缓慢)来解决问题
  • 如果时间有限,则将cins和couts更改为printf和scanf(如果使用string类出现问题,请像这样打印:printf(%s,mystr.c_str());)
  • 如果仍然时间不够,则尝试进行一些明显的优化(例如避免嵌套过多的for / while / dowhile或递归函数)。还要确保通过引用传递过大的对象...
  • 如果仍然时间不够,则尝试将std :: vectors和sets更改为c-arrays。
  • 如果仍然时间不够,则继续下一个问题...

0
问题在于,cin 有很多开销,因为它在 scanf() 调用之上提供了一个抽象层。如果你正在编写 C++ 软件,就不应该使用 scanf() 而不是 cin,因为这就是 cin 的作用。如果你想要性能,你可能不会在 C++ 中编写 I/O。

2
“cin”在运行时比“scanf”更“抽象”吗?我不这么认为...“scanf”必须在运行时解释格式字符串,而“iostream”在编译时就知道了格式。 - nibot
1
@nibot:编译时已知类型,但不知道格式。例如,输入是否应为十六进制完全取决于在运行时如何配置std::istream(通过I/O操作符或通过在istream对象本身上设置标志)。另一方面,FILE*对象没有这种状态,因此在这方面调用scanf更加稳定。 - dreamlax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接