Używanie scanf () w programach C ++ jest szybsze niż używanie cin?

126

Nie wiem, czy to prawda, ale kiedy czytałem FAQ na temat jednej z problemów z udostępnianiem stron, znalazłem coś, co zwróciło moją uwagę:

Sprawdź metody wejścia / wyjścia. W C ++ używanie cin i cout jest zbyt wolne. Użyj ich, a zagwarantujesz, że nie będziesz w stanie rozwiązać żadnego problemu za pomocą przyzwoitej ilości danych wejściowych lub wyjściowych. Zamiast tego użyj printf i scanf.

Czy ktoś może to wyjaśnić? Czy naprawdę używanie scanf () w programach C ++ jest szybsze niż używanie cin >> coś ? Jeśli tak, czy dobrze jest używać go w programach C ++? Myślałem, że to specyficzne dla C, chociaż dopiero uczę się C ++ ...

zeroDivisible
źródło
14
Domyślam się: zły programista obwinia standardowe biblioteki za słabą wydajność. Coś jak zawsze humorystyczny okrzyk „Myślę, że znalazłem błąd w GCC”.
John Kugelman
11
@eclipse: problemy ACM, nad którymi pracowałem podczas konkursów, mają znaczną ilość danych wejściowych / wyjściowych, a Twój program musi rozwiązać pytania w ciągu około 60 sekund ... tutaj staje się prawdziwym problemem.
mpen
19
--- to powiedziawszy, jeśli musisz polegać na scanf (), aby uzyskać dodatkowy wzrost wydajności, rozwiązujesz problem w niewłaściwy sposób :)
mpen
4
Tak jak obserwacja - bawiłem się tym i przy problemach 2 (PRIME1) - używając tego samego algorytmu, za każdym razem, raz z cin / cout i raz z scanf / printf i pierwsza wersja była szybsza niż druga (ale na tyle blisko, że nie ma to znaczenia statystycznego). Jest to jeden z problemów, który został określony jako wymagający dużego nakładu / produkcji, a metoda nakładów / wyników nie powodowała żadnych statystycznych różnic.
Eclipse
4
@Eclipse - dzięki za informację o testowaniu obu metod. Jestem jednak smutny - próbowałem winić cin i cout, ale teraz wiem, że mój algorytm jest do bani :)
zeroDivisible

Odpowiedzi:

209

Oto szybki test prostego przypadku: program do odczytywania listy liczb ze standardowego wejścia i XOR wszystkich liczb.

Wersja 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;
}

wersja 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;
}

Wyniki

Korzystając z trzeciego programu, wygenerowałem plik tekstowy zawierający 33 280 276 liczb losowych. Czasy realizacji to:

iostream version:  24.3 seconds
scanf version:      6.4 seconds

Zmiana ustawień optymalizacji kompilatora w ogóle nie zmieniła wyników.

Zatem: naprawdę istnieje różnica prędkości.


EDYCJA: Użytkownik clyfish wskazuje poniżej, że różnica prędkości jest w dużej mierze spowodowana funkcjami I / O iostream utrzymującymi synchronizację z funkcjami CI / O. Możemy to wyłączyć, dzwoniąc do 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;
}

Nowe wyniki:

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

C ++ iostream wygrywa! Okazuje się, że ta wewnętrzna synchronizacja / opróżnianie jest tym, co zwykle spowalnia iostream we / wy. Jeśli nie mieszamy stdio i iostream, możemy to wyłączyć, a wtedy iostream jest najszybszy.

Kod: https://gist.github.com/3845568

nibot
źródło
6
Myślę, że użycie „endl” może spowolnić wykonanie.
Krishna Mohan
2
Użycie std :: endl nie jest w pętli.
nibot
Nie ma znaczenia, czy synchronizacja jest włączona czy wyłączona. Winić za to libc ++. To tylko zwiększa libstdc ++
iBug
Czy uważasz, że byłaby jakaś różnica między <cstdio> a <stdio.h>?
Chandrahas Aroori
iostreamprzegrywa, gdy analizujesz więcej niż jedną liczbę całkowitą w jednym scanfwywołaniu.
Maxim Egorushkin
68

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

Wydajność cin/ coutmoże być powolna, ponieważ muszą być zsynchronizowani z podstawową biblioteką C. Jest to niezbędne, jeśli mają być używane zarówno C IO, jak i C ++ IO.

Jeśli jednak zamierzasz używać tylko C ++ IO, po prostu użyj poniższego wiersza przed operacjami IO.

std::ios::sync_with_stdio(false);

Aby uzyskać więcej informacji na ten temat, zapoznaj się z odpowiednią dokumentacją libstdc ++ .

liszaj
źródło
Właśnie sprawdziłem powyższą linię (std :: ios :: sync_with_stdio (false);) I to naprawdę sprawia, że ​​iostream jest prawie tak szybki jak cstdio
gabrielhidasy
użyj także cin.tie (static_cast <ostream *> (0)); dla lepszej wydajności
Mohamed El-Nakib,
42

Prawdopodobnie scanf jest nieco szybszy niż używanie strumieni. Chociaż strumienie zapewniają duże bezpieczeństwo typów i nie muszą analizować ciągów formatu w czasie wykonywania, zwykle ma tę zaletę, że nie wymaga nadmiernego przydziału pamięci (zależy to od kompilatora i środowiska uruchomieniowego). To powiedziawszy, chyba że wydajność jest Twoim jedynym celem i znajdujesz się na ścieżce krytycznej, naprawdę powinieneś faworyzować bezpieczniejsze (wolniejsze) metody.

Jest bardzo smaczne artykuł napisany tutaj przez Herb Sutter „ Ciąg formatek z Manor Farm ”, który przechodzi w wielu szczegółach z wykonania ciągów formatujących jak sscanfi lexical_casti jakie rzeczy robili im działać wolniej lub szybciej. Jest to trochę analogiczne, prawdopodobnie do rodzaju rzeczy, które wpływałyby na wydajność między stylem IO w stylu C i stylem C ++. Główną różnicą w stosunku do elementów formatujących było bezpieczeństwo typów i liczba alokacji pamięci.

1800 INFORMACJE
źródło
19

Właśnie spędziłem wieczór pracując nad problemem w UVa Online (Factovisors, bardzo ciekawy problem, sprawdź to):

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

Otrzymywałem TLE (przekroczono limit czasu) na moje zgłoszenia. W tych witrynach z sędziami online do rozwiązywania problemów masz około 2-3 sekund na obsłużenie potencjalnie tysięcy przypadków testowych używanych do oceny Twojego rozwiązania. W przypadku problemów wymagających dużej mocy obliczeniowej, takich jak ten, liczy się każda mikrosekunda.

Używałem sugerowanego algorytmu (o którym możesz przeczytać na forach dyskusyjnych w witrynie), ale nadal otrzymywałem TLE.

Zmieniłem tylko „cin >> n >> m” na „scanf („% d% d ”, & n, & m)” i kilka małych „coutów” na „printfs”, a mój TLE zmienił się na „Zaakceptowano”!

Więc tak, może to mieć duże znaczenie, zwłaszcza gdy terminy są krótkie.

Bogatyr
źródło
Zgodzić się. To samo przytrafiło mi się w problemie UVA Online Judge: Army Buddies uva.onlinejudge.org/…
Mohamed El-Nakib
6

Jeśli zależy Ci na wydajności i formatowaniu ciągów, spójrz na bibliotekę FastFormat Matthew Wilsona .

edit - link do publikacji accu w tej bibliotece: http://accu.org/index.php/journals/1539

xtofl
źródło
Zgadzam się całkowicie. Ale musisz mieć świadomość, że FastFormat służy tylko do drukowania. Nie ma możliwości wprowadzania / odczytu. (W każdym razie jeszcze nie)
DCW
Niestety ten link wydaje się być martwy. Oto kopia Wayback Machine: web.archive.org/web/20081222164527/http://fastformat.org
nibot
2

Istnieją implementacje stdio ( libio ), które implementują PLIK * jako strumień C ++ i fprintf jako parser formatu środowiska wykonawczego. IOstreams nie wymagają analizowania formatu w czasie wykonywania, wszystko to jest wykonywane w czasie kompilacji. Tak więc, przy współużytkowaniu backendów, rozsądnie jest oczekiwać, że iostreams jest szybszy w czasie wykonywania.

MSalters
źródło
Nie sądzę. Myślę, że biblioteka GNU to czysty C i assembler.
Chris Lutz,
2

Tak, iostream jest wolniejszy niż cstdio.
Tak, prawdopodobnie nie powinieneś używać cstdio, jeśli tworzysz w C ++.
Powiedziawszy to, istnieją nawet szybsze sposoby na uzyskanie I / O niż scanf, jeśli nie zależy ci na formatowaniu, bezpieczeństwie typów, bla, bla, bla ...

Na przykład jest to niestandardowa procedura pobierania numeru z 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
źródło
1
getchar_unlocked () jest niestandardowa i dostępna dla gcc, a nie Visual Studio
Mohamed El-Nakib,
2

Instrukcje cini coutogólne użycie wydają się być wolniejsze niż scanfiw printfC ++, ale w rzeczywistości są SZYBSZE!

Chodzi o to, że w C ++ za każdym razem, gdy używasz cini cout, domyślnie odbywa się proces synchronizacji, który zapewnia, że ​​jeśli używasz obu scanfiw cinswoim programie, to oba będą ze sobą zsynchronizowane. Ten proces synchronizacji wymaga czasu. Stąd cini coutWYGLĄDA na wolniejsze.

Jeśli jednak proces synchronizacji nie cinjest wykonywany , jest szybszy niż scanf.

Aby pominąć proces synchronizacji, umieść w swoim programie następujący fragment kodu na samym początku main():

std::ios::sync_with_stdio(false);

Odwiedź tę witrynę, aby uzyskać więcej informacji.

Prasoon Varshney
źródło
+1 za wyjaśnienie dotyczące synchronizacji. Właśnie wyłączyłem synchronizację i użyłem zarówno scanf, jak i cin w jakimś kodzie . teraz wiem, co było z tym nie tak. Dziękuję Ci!
Dariush
1

Problem polega na tym, że cinwiąże się to z dużym narzutem, ponieważ zapewnia warstwę abstrakcji nad scanf()wywołaniami. Nie powinieneś używać scanf()overu, cinjeśli piszesz oprogramowanie w C ++, ponieważ jest to pożądane cin. Jeśli chcesz wydajności, prawdopodobnie i tak nie pisałbyś I / O w C ++.

dreamlax
źródło
2
Czy cinnaprawdę jest bardziej „abstrakcyjny” (w czasie wykonywania) niż scanf? Nie sądzę ... scanfmusi interpretować ciąg formatu w czasie wykonywania, podczas iostreamgdy format zna format w czasie kompilacji.
nibot
1
@nibot: typ jest znany w czasie kompilacji, ale nie jest znany format . To, czy wejście ma być na przykład szesnastkowe, czy nie, jest całkowicie zależne od tego, jak std::istreamjest skonfigurowane w czasie wykonywania (przez manipulatory we / wy lub przez ustawienie flag na samym istreamobiekcie). Z FILE*drugiej strony obiekt nie ma takiego stanu, więc wywołanie scanfw tym zakresie jest dużo bardziej stabilne.
dreamlax
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;
}

Na końcu pliku jest błąd, ale ten kod w C jest znacznie szybszy niż szybsza wersja 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

Oryginalny C ++ zajmował 30 sekund, a kod C 2 sekundy.

hexec
źródło
-1

Oczywiście używanie cstdio na iostream jest śmieszne. Przynajmniej kiedy tworzysz oprogramowanie (jeśli już używasz c ++ zamiast c, to idź na całość i wykorzystaj jego zalety, a nie tylko cierpienie z powodu jego wad).

Ale w ocenie online, że nie tworzysz oprogramowania, tworzysz program, który powinien być w stanie robić rzeczy, które oprogramowanie Microsoft zajmuje 60 sekund w 3 sekundy !!!

Tak więc w tym przypadku złota zasada wygląda tak (oczywiście, jeśli nie masz jeszcze większych kłopotów, używając javy)

  • Użyj c ++ i wykorzystaj całą jego moc (i ciężkość / powolność), aby rozwiązać problem
  • Jeśli masz ograniczony czas, zmień cins i couts dla printfs i scanfs (jeśli spieprzysz, używając ciągu klasy, wydrukuj w ten sposób: printf (% s, mystr.c_str ());
  • Jeśli nadal masz ograniczony czas, spróbuj dokonać pewnych oczywistych optymalizacji (takich jak unikanie zbyt wielu osadzonych funkcji / while / downhiles lub funkcji rekurencyjnych). Upewnij się również, że przekazujesz obiekty referencyjne, które są zbyt duże ...
  • Jeśli nadal masz ograniczony czas, spróbuj zmienić std :: vectors i sety dla c-arrays.
  • Jeśli nadal masz ograniczony czas, przejdź do następnego problemu ...
Carlos Pacheco
źródło
-2

Nawet gdyby scanfbył szybszy niż cin, nie miałoby to znaczenia. Przez większość czasu będziesz czytać z dysku twardego lub klawiatury. Wprowadzenie surowych danych do aplikacji zajmuje rzędy wielkości więcej czasu niż ich przetwarzanie scanflub cinich przetworzenie.

Jay Conrod
źródło
A co z IPC przez rury? Czy uważasz, że może tam być zauważalny hit wydajnościowy?
dreamlax
Nawet przy IPC przez potoki, dużo więcej czasu spędza się na wchodzeniu i wychodzeniu z jądra, niż tylko na parsowaniu go za pomocą scanf / cin.
Jay Conrod,
8
Zrobiłem testy w tym obszarze i na pewno cout & cin suck performance. Chociaż w przypadku danych wejściowych użytkownika jest to pomijalne, z pewnością nie dotyczy rzeczy, w których liczy się wydajność. Istnieją jednak inne frameworki C ++, które są szybsze.
Johannes Schaub - litb
Problem w tym, że iostream jest wolniejszy niż dysk twardy. Tak, to jest do bani.
polkovnikov.ph