Usar scanf () em programas C ++ é mais rápido que usar cin?

126

Não sei se isso é verdade, mas quando estava lendo as Perguntas frequentes sobre um dos sites que forneciam problemas, encontrei algo que chamou minha atenção:

Verifique seus métodos de entrada / saída. No C ++, o uso de cin e cout é muito lento. Use-os e você garantirá não conseguir resolver nenhum problema com uma quantidade razoável de entrada ou saída. Use printf e scanf.

Alguém pode esclarecer isso? Realmente está usando scanf () em programas C ++ mais rápido do que algo cin >> ? Se sim, é uma boa prática usá-lo em programas C ++? Eu pensei que era específico C, embora eu esteja apenas aprendendo C ++ ...

zeroDivisible
fonte
14
Meu palpite: programador ruim culpa as bibliotecas padrão pelo mau desempenho. Mais ou menos como o sempre engraçado humor "Acho que encontrei um bug no GCC".
John Kugelman
11
@eclipse: os problemas do ACM em que trabalhei para competições têm uma quantidade substancial de entradas / saídas e seu programa precisa resolver as questões em menos de 60 segundos ... isso se torna um problema real aqui.
MJ # 06:
19
--- o que disse, se você precisa confiar em scanf () para esse aumento de desempenho extra, você está indo sobre o problema da maneira errada :)
MPEN
4
Apenas como uma observação - eu brinquei com ele e no segundo problema (PRIME1) - usando o mesmo algoritmo, nas duas vezes, uma vez usando cin / cout e outra com scanf / printf e a primeira versão foi mais rápida que a segunda (mas perto o suficiente para ser estatisticamente irrelevante). Esse é um dos problemas marcados como intensivos em entrada / saída, e o método de entrada / saída não fez diferença estatística.
Eclipse
4
@ Eclipse - obrigado pelas informações sobre o teste dos dois métodos. Estou triste embora - Tentei culpa cin e cout, mas agora eu sei que o meu algoritmo suga :)
zeroDivisible

Respostas:

209

Aqui está um teste rápido de um caso simples: um programa para ler uma lista de números da entrada padrão e XOR todos os números.

versão 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;
}

versão 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;
}

Resultados

Usando um terceiro programa, eu gerei um arquivo de texto contendo 33.280.276 números aleatórios. Os tempos de execução são:

iostream version:  24.3 seconds
scanf version:      6.4 seconds

Alterar as configurações de otimização do compilador não pareceu alterar muito os resultados.

Assim: realmente existe uma diferença de velocidade.


EDIT: O usuário clyfish indica abaixo que a diferença de velocidade se deve em grande parte às funções de E / S iostream, que mantêm a sincronização com as funções de CI / O. Podemos desativar isso com uma chamada para 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;
}

Novos resultados:

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

C ++ iostream vence! Acontece que essa sincronização / liberação interna é o que normalmente reduz a velocidade de E / S do iostream. Se não estivermos misturando stdio e iostream, podemos desativá-lo e o iostream é o mais rápido.

O código: https://gist.github.com/3845568

nibot
fonte
6
Eu acho que o uso de 'endl' pode retardar a execução.
Krishna Mohan
2
O uso de std :: endl não está no loop.
Nibot
Não faz diferença com a sincronização ativada ou desativada. Culpe a libc ++ por isso. Ele apenas aumenta o libstdc ++
iBug 16/18
Você acha que existe alguma diferença entre <cstdio> e <stdio.h> ??
Chandrahas Aroori
iostreamperde quando você analisa mais de um número inteiro em uma scanfchamada.
Maxim Egorushkin
68

http://www.quora.com/Is-cin-cout-slower-than-scanf-printf/answer/Aditya-Vishwakarma

O desempenho de cin/ coutpode ser lento porque eles precisam se manter sincronizados com a biblioteca C subjacente. Isso é essencial se o C IO e o C ++ IO forem usados.

No entanto, se você usar apenas C ++ IO, basta usar a linha abaixo antes de qualquer operação de IO.

std::ios::sync_with_stdio(false);

Para obter mais informações, consulte os documentos correspondentes do libstdc ++ .

clyfish
fonte
Verifiquei apenas a linha acima (std :: ios :: sync_with_stdio (false);) E realmente fazer iostream quase tão rápido quanto cstdio
gabrielhidasy
use também cin.tie (static_cast <ostream *> (0)); para um melhor desempenho
Mohamed El-Nakib
42

Provavelmente o scanf é um pouco mais rápido do que usar fluxos. Embora os fluxos forneçam muita segurança de tipo e não precisem analisar seqüências de caracteres de formato no tempo de execução, ele geralmente tem a vantagem de não exigir alocações de memória excessivas (isso depende do compilador e do tempo de execução). Dito isto, a menos que o desempenho seja seu único objetivo final e você esteja no caminho crítico, deve realmente favorecer os métodos mais lentos.

Há um artigo muito delicioso, escrito aqui por Herb Sutter " Os formatadores de cordas da Manor Farm ", que aborda muitos detalhes do desempenho de formatadores de cordas sscanfe como lexical_caste que tipo de coisas os fazia correr lenta ou rapidamente. Isso é análogo, provavelmente ao tipo de coisas que afetariam o desempenho entre o estilo C IO e o estilo C ++. A principal diferença com os formatadores costumava ser o tipo de segurança e o número de alocações de memória.

1800 INFORMAÇÃO
fonte
19

Passei uma noite trabalhando em um problema no UVa Online (Factovisors, um problema muito interessante, confira):

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

Eu estava recebendo o TLE (limite de tempo excedido) em meus envios. Nesses sites de juízes on-line para solução de problemas, você tem um prazo de 2 a 3 segundos para lidar com potencialmente milhares de casos de teste usados ​​para avaliar sua solução. Para problemas computacionalmente intensivos como este, cada microssegundo conta.

Eu estava usando o algoritmo sugerido (leia nos fóruns de discussão do site), mas ainda estava obtendo TLEs.

Alterei apenas "cin >> n >> m" para "scanf ("% d% d ", & n, & m)" e os poucos "couts" pequenos para "printfs", e meu TLE se transformou em "Aceito"!

Então, sim, isso pode fazer uma grande diferença, especialmente quando os prazos são curtos.

Bogatyr
fonte
Aceita. O mesmo aconteceu comigo no problema do UVA Online Judge: Army Buddies uva.onlinejudge.org/…
Mohamed El-Nakib
6

Se você se preocupa com o desempenho e a formatação de cadeias, consulte a biblioteca FastFormat de Matthew Wilson .

edit - link para publicação accu nessa biblioteca: http://accu.org/index.php/journals/1539

xtofl
fonte
Concordo completamente. Mas você precisa estar ciente de que o FastFormat é apenas para saída. Não possui recursos de entrada / leitura. (Ainda não, de qualquer maneira)
DCW
Infelizmente esse link parece estar morto. Aqui está uma cópia Wayback Machine: web.archive.org/web/20081222164527/http://fastformat.org
nibot
2

Existem implementações stdio ( libio ) que implementam FILE * como um streambuf C ++ e fprintf como um analisador de formato de tempo de execução. Os IOstreams não precisam de análise de formato de tempo de execução, tudo é feito em tempo de compilação. Portanto, com os back-ends compartilhados, é razoável esperar que os iostreams sejam mais rápidos no tempo de execução.

MSalters
fonte
Acho que não. Eu acho que o libc do GNU é puro C e assembly.
22720 Chris Lutz
2

Sim, o iostream é mais lento que o cstdio.
Sim, você provavelmente não deve usar o cstdio se estiver desenvolvendo em C ++.
Dito isso, existem maneiras ainda mais rápidas de obter E / S do que o scanf, se você não se importa com formatação, digite safety, blá, blá, blá ...

Por exemplo, esta é uma rotina personalizada para obter um número de 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;
}
pedro.lupin
fonte
1
getchar_unlocked () é não-padrão, e disponível para gcc não visual studio
Mohamed El-Nakib
1

O problema é que cinhá muita sobrecarga envolvida porque fornece uma camada de abstração acima das scanf()chamadas. Você não deve usar scanf()mais cinse estiver escrevendo software C ++, porque é isso que é necessário cin. Se você deseja desempenho, provavelmente não estaria escrevendo E / S em C ++.

dreamlax
fonte
2
É cinrealmente mais "abstrato" (em tempo de execução) do que scanf? Eu acho que não ... scanfdeve interpretar a string de formato em tempo de execução, enquanto que iostreamconhece o formato em tempo de compilação.
Nibot
1
@ nibot: o tipo é conhecido em tempo de compilação, mas não o formato . Se a entrada deve ser hexadecimal ou não, por exemplo, depende inteiramente de como ela std::istreamé configurada no tempo de execução (por meio de manipuladores de E / S ou pela configuração de sinalizadores no istreampróprio objeto). Um FILE*objeto, por outro lado, não possui esse estado; portanto, uma chamada a scanfesse respeito é muito mais estável.
dreamlax
1

As demonstrações cine coutde uso geral parece ser mais lento do que scanfe printfem C ++, mas na verdade eles são mais rápidos!

A questão é: no C ++, sempre que você usa cine cout, ocorre um processo de sincronização por padrão que garante que, se você usa o programa scanfe cino programa, ambos trabalhem em sincronia. Esse processo de sincronização leva tempo. Portanto, cine coutparecer mais lento.

No entanto, se o processo de sincronização estiver definido para não ocorrer, cinserá mais rápido que scanf.

Para ignorar o processo de sincronização, inclua o seguinte snippet de código no seu programa logo no início de main():

std::ios::sync_with_stdio(false);

Visite este site para mais informações.

Prasoon Varshney
fonte
+1 para sua explicação sobre sincronização. Acabei de desativar a sincronização e usei scanf e cin em algum código . agora eu sei o que havia de errado com isso. obrigado!
Dariush
1
#include <stdio.h>
#include <unistd.h>

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

static int scanuint(unsigned int* x)
{
  char c;
  *x = 0;

  do
  {
      c = getchar_unlocked();
      if (unlikely(c==EOF)) return 1;
  } while(c<'0' || c>'9');

  do
  {
      //*x = (*x<<3)+(*x<<1) + c - '0';
      *x = 10 * (*x) + c - '0';
      c = getchar_unlocked();
      if (unlikely(c==EOF)) return 1;
  } while ((c>='0' && c<='9'));

  return 0;
}

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

  int parity = 0;
  unsigned int x;

  while (1 != (scanuint(&x))) {
    parity ^= x;
  }
  parity ^=x;
  printf("%d\n", parity);

  return 0;
}

Há um erro no final do arquivo, mas esse código C é muito mais rápido que a versão mais rápida do C ++.

paradox@scorpion 3845568-78602a3f95902f3f3ac63b6beecaa9719e28a6d6  make test        
time ./xor-c < rand.txt
360589110

real    0m11,336s
user    0m11,157s
sys 0m0,179s
time ./xor2-c < rand.txt
360589110

real    0m2,104s
user    0m1,959s
sys 0m0,144s
time ./xor-cpp < rand.txt
360589110

real    0m29,948s
user    0m29,809s
sys 0m0,140s
time ./xor-cpp-noflush < rand.txt
360589110

real    0m7,604s
user    0m7,480s
sys 0m0,123s

O C ++ original levou 30 segundos e o código C levou 2 segundos.

hexec
fonte
-1

Claro que é ridículo usar o cstdio sobre o iostream. Pelo menos quando você desenvolve software (se você já estiver usando c ++ sobre c, vá até o fim e use seus benefícios, em vez de sofrer apenas pelas desvantagens).

Mas, no juiz on-line, você não está desenvolvendo software, está criando um programa que deve ser capaz de fazer as coisas que o software Microsoft leva 60 segundos para atingir em 3 segundos !!!

Portanto, neste caso, a regra de ouro é a seguinte (é claro, se você não tiver mais problemas usando java)

  • Use c ++ e use todo o seu poder (e peso / lentidão) para resolver o problema
  • Se você tiver um tempo limitado, altere os arquivos e os cupons para printfs e scanfs (se você errar usando a string de classe, imprima da seguinte maneira: printf (% s, mystr.c_str ());
  • Se você ainda tiver tempo limitado, tente fazer algumas otimizações óbvias (como evitar muitas incorporadas por / enquanto / dowhiles ou funções recursivas). Certifique-se também de passar por objetos de referência que são muito grandes ...
  • Se você ainda tiver tempo limitado, tente alterar os vetores e conjuntos std :: para matrizes-c.
  • Se você ainda tiver tempo limitado, prossiga para o próximo problema ...
Carlos Pacheco
fonte
-2

Mesmo se scanffosse mais rápido que cinisso, não importaria. Na grande maioria das vezes, você estará lendo no disco rígido ou no teclado. Colocar os dados brutos em seu aplicativo leva ordens de magnitude mais tempo do que leva scanfou cinpara processá-las.

Jay Conrod
fonte
E o IPC através de tubos? Você acha que pode haver um desempenho perceptível?
dreamlax
Mesmo com o IPC através de pipes, é gasto muito mais tempo entrando e saindo do kernel do que apenas analisando-o com scanf / cin.
Jay Conrod
8
Fiz testes nessa área e, certamente, cout & cin sugam o desempenho. Embora a entrada do usuário seja insignificante, certamente não é o que acontece em questões de desempenho. No entanto, existem outras estruturas c ++ que são mais rápidas.
Johannes Schaub - litb 25/06/09
O problema é que iostream é mais lento que o disco rígido. Sim, é péssimo.
polkovnikov.ph