Використання scanf () у програмах C ++ швидше, ніж використання cin?


126

Я не знаю, чи це правда, але коли я читав FAQ на одному із проблемних сайтів, я знайшов щось, що викликає мою увагу:

Перевірте способи введення / виводу. У C ++ використання cin та cout занадто повільне. Використовуйте ці, і ви гарантуєте, що не зможете вирішити жодну проблему з пристойним обсягом вводу чи виводу. Використовуйте замість printf та scanf.

Може хтось прохання уточнити це? Чи справді використання scanf () у програмах C ++ швидше, ніж використання cin >> чогось ? Якщо так, то чи корисно це використовувати в програмах C ++? Я думав, що це специфічно для C, хоча я просто вивчаю C ++ ...


14
Моя здогадка: поганий програміст звинувачує стандартні бібліотеки у низькій продуктивності. Такий, як завжди жартівливий "я думаю, що я знайшов помилку в GCC" плач.
Джон Кугельман

11
@eclipse: проблеми АСМ, над якими я працював на змаганнях, мають значну кількість вводу / виводу, і ваша програма повинна вирішувати питання за щось на кшталт 60 секунд ... це стає справжнім питанням.
09.09

19
--- що сказало, якщо вам потрібно покластися на scanf () для цього додаткового підвищення продуктивності, ви
підете

4
Як спостереження - я пограв з цим, і щодо 2-х проблем (PRIME1) - використовуючи той самий алгоритм, обидва рази, один раз використовуючи cin / cout і один раз за допомогою Scanf / printf, і перша версія була швидшою, ніж друга (але достатньо близько, що це статистично не має значення). Це одна з проблем, яка позначається як введення / виведення, а метод введення / виведення не має жодної статистичної різниці.
Затемнення

4
@Eclipse - дякую за інформацію про тестування обох методів. Мені все сумно - я намагався звинуватити cin і cout, але тепер я знаю, що мій алгоритм смокче :)
zeroDivisible

Відповіді:


209

Ось швидкий тест простого випадку: програма для зчитування списку чисел зі стандартного введення та 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

Зміна параметрів оптимізації компілятора, схоже, зовсім не змінила результати.

Таким чином: дійсно є різниця швидкостей.


EDIT: Користувач clyfish вказує нижче, що різниця швидкостей значною мірою пов'язана з функціями iostream I / O, що підтримують синхронізацію з функціями CI / 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


6
Я думаю, що використання 'endl' може уповільнити виконання.
Крішна Мохан

2
Використання std :: endl не знаходиться в циклі.
nibot

Немає різниці із увімкненою чи вимкненою синхронізацією. За це звинувачуйте libc ++. Це підвищує лише libstdc ++
iBug

Як ви думаєте, була б різниця між <cstdio> і <stdio.h> ??
Чандрахас Арурі

iostreamвтрачається, коли ви розбираєте більше одного цілого числа в одному scanfдзвінку.
Максим

68

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

Виконання cin/ coutможе бути повільним, оскільки їм потрібно підтримувати синхронізацію з базовою бібліотекою C. Це важливо, якщо будуть використовуватися як C IO, так і C ++ IO.

Однак якщо ви збираєтесь використовувати лише C ++ IO, то просто використовуйте нижченаведений рядок перед будь-якими операціями вводу-виводу.

std::ios::sync_with_stdio(false);

Щоб отримати докладнішу інформацію про це, подивіться відповідні документи libstdc ++ .


Просто перевірив рядок вище (std :: ios :: sync_with_stdio (false);) І це дійсно зробить iostream майже так само швидко, як cstdio
gabrielhidasy

також використовувати cin.tie (static_cast <ostream *> (0)); для кращої роботи
Мохаммед Ель-Накіб

42

Можливо, scanf дещо швидше, ніж використання потоків. Хоча потоки забезпечують велику безпеку типу і не потребують розбору рядків формату під час виконання, зазвичай це має перевагу в тому, що він не вимагає надмірного розміщення пам'яті (це залежить від вашого компілятора та часу виконання). Однак, якщо ефективність не є вашою єдиною ціллю, і ви перебуваєте на критичному шляху, тоді вам слід віддати перевагу більш безпечним (повільнішим) методам.

Існує дуже смачна стаття написано тут Херб Саттер « Рядок форматування садибної Ферми » , який входить в багатьох деталей виконання струнних форматування як sscanfі lexical_castі які речі були роблять їх бігти швидко або повільно. Це свого роду аналог, ймовірно, з тими речами, які впливали б на продуктивність між стилем C I I та C ++. Основна відмінність від форматорів, як правило, полягала в безпеці типу та кількості розподілу пам'яті.


19

Я щойно провів вечір, працюючи над проблемою на 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)" і кілька крихітних "кутів" на "printfs", і мій TLE перетворився на "Прийнято"!

Так, так, це може змінити велику вагу, особливо коли обмежені строки.


Погодьтеся. Те саме трапилося і з проблемою судді Інтернет UVA: Армія приятелів uva.onlinejudge.org/…
Мохамед Ель-Накіб

6

Якщо ви ставитесь до продуктивності та форматування рядків, погляньте на бібліотеку Меттью Вілсона FastFormat .

редагувати - посилання на публікацію обвинувачення в цій бібліотеці: http://accu.org/index.php/journals/1539


Погодьтеся повністю. Але вам потрібно знати, що FastFormat призначений лише для виводу. У ньому немає засобів введення / зчитування. (Ще ні, все одно)
dcw

На жаль, це посилання, здається, мертве. Ось копія зворотної
nibot

2

Є реалізація stdio ( libio ), яка реалізує FILE * як потоковий файл C ++, а fprintf як аналізатор формату виконання. IOstreams не потребують розбору формату виконання, це все робиться під час компіляції. Тож, із загальним розпорядженням, розумно розраховувати, що іострими будуть швидшими під час виконання.


Я не думаю, що так. Я думаю, що libc GNU - це чисто C і збірка.
Кріс Лутц

2

Так, іостріум повільніше, ніж cstdio.
Так, ви, ймовірно, не повинні використовувати cstdio, якщо ви розробляєте C ++.
Сказавши це, є навіть більш швидкі способи отримати введення-виведення, ніж scanf, якщо вам не байдуже форматування, безпека типу, бла, бла, бла ...

Наприклад, це звичайний звичайний спосіб отримання номера з 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 not visual studio
Mohamed El-Nakib

2

Заяви cinі coutв загальному використанні , здається, повільніше , ніж scanfта printfв C ++, але на самому ділі вони є ШВИДШЕ!

Справа в тому, що в C ++ кожен раз, коли ви користуєтесь cinі cout, процес синхронізації відбувається за замовчуванням, що гарантує, що якщо ви використовуєте і те, scanfі cinв своїй програмі, вони обидва працюють синхронно один з одним. Цей процес синхронізації потребує часу. Значить, cinі coutЗДОРОВИТИ бути повільніше.

Однак якщо процес синхронізації встановлено не відбувається, cinце швидше, ніж scanf.

Щоб пропустити процес синхронізації, включіть наступний фрагмент коду у свою програму прямо на початку main():

std::ios::sync_with_stdio(false);

Відвідайте цей сайт для отримання додаткової інформації.


+1 для вашого пояснення щодо синхронізації. Я щойно вимкнув синхронізацію і використав і scanf, і cin у якомусь коді . тепер я знаю, що з цим було не так. спасибі!
Даріуш

1

Проблема полягає cinв тому, що задіяно багато накладних витрат, оскільки це дає вам рівень абстракції над scanf()викликами. Ви не повинні використовувати scanf()більше, ніж cinви пишете програмне забезпечення C ++, оскільки це є бажанням cin. Якщо ви хочете виконання, ви, ймовірно, не писали б введення / виведення на C ++.


2
Чи cinсправді більш "абстрактно" (під час виконання), ніж scanf? Я не думаю, що так ... scanfповинен інтерпретувати рядок формату під час виконання, тоді як iostreamзнає формат під час компіляції.
nibot

1
@nibot: Тип відомий під час компіляції, але не у форматі . Очікується, наприклад, вхід буде шістнадцятковим чи ні, наприклад, повністю залежить від того, як std::istreamконфігурується під час виконання (за допомогою маніпуляторів вводу / виводу або встановлення прапорців на сам istreamоб’єкт). FILE*Об'єкта з іншого боку , не має такого стану, так що виклик scanfв цьому відношенні є набагато більш стабільним.
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;
}

Помилка є в кінці файлу, але цей код C істотно швидший, ніж швидша версія 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

Оригінальний C ++ займав 30сек. Код C займав 2 сек.


-1

Звичайно, смішно використовувати cstdio над iostream. Принаймні, коли ви розробляєте програмне забезпечення (якщо ви вже використовуєте c ++ понад c, тоді пройдіть весь шлях і використовуйте його переваги, а не страждайте лише від його недоліків).

Але в онлайн-судді ви не розробляєте програмне забезпечення, ви створюєте програму, яка повинна вміти робити те, що для програмного забезпечення Microsoft потрібно 60 секунд, щоб досягти за 3 секунди !!!

Отже, у такому випадку золоте правило виглядає так (звичайно, якщо ви не потрапляєте у ще більше проблем, використовуючи java)

  • Використовуйте c ++ і використовуйте всю його потужність (і важкість / повільність) для вирішення проблеми
  • Якщо у вас обмежений час, то змініть посилання і куточки для printfs та scanfs (якщо вас закрутили за допомогою рядка класу, друкуйте так: printf (% s, mystr.c_str ());
  • Якщо у вас все ще обмежений час, спробуйте зробити кілька очевидних оптимізацій (наприклад, уникати занадто багато вбудованих функцій / while / dowchiles або рекурсивних функцій). Також переконайтеся, що проходять повз об'єкти, які занадто великі ...
  • Якщо у вас все ще обмежений час, спробуйте змінити std :: vectors та набори для c-масивів.
  • Якщо у вас все ще обмежений час, перейдіть до наступної проблеми ...

-2

Навіть якби вони scanfбули швидшими cin, це не мало значення. Переважну більшість часу ви будете читати з жорсткого диска або клавіатури. Отримання необроблених даних у вашу програму вимагає наказів більше, ніж потрібно scanfабо cinдля їх обробки.


Що з IPC через труби? Як ви думаєте, там може бути помітний хіт виступу?
dreamlax

Навіть при IPC через труби витрачається набагато більше часу на вхід і вихід з ядра, ніж просто на його аналіз скануванням / cin.
Джей Конрод

8
Я робив тести в цій галузі, і, безумовно, продуктивність смоктання & cin. Хоча для введення користувача це незначно, це, звичайно, не так для речей, де продуктивність має значення. Однак існують інші рамки c ++, які є швидшими.
Йоханнес Шауб - ліб

Проблема в тому, що iostream вона повільніше, ніж HDD. Так, це так смокче.
polkovnikov.ph
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.