Использование scanf () в программах на C ++ быстрее, чем использование cin?

126

Не знаю, правда ли это, но когда я читал FAQ по одному из проблемных сайтов, я нашел кое-что, что привлекло мое внимание:

Проверьте свои методы ввода / вывода. В C ++ использование cin и cout слишком медленное. Используйте их, и вы гарантированно не сможете решить любую проблему с приличным объемом ввода или вывода. Вместо этого используйте printf и scanf.

Может кто-нибудь прояснить это? Действительно ли использование scanf () в программах на C ++ быстрее, чем использование cin >> something ? Если да, то стоит ли использовать его в программах на C ++? Я думал, что это специфично для C, хотя я только изучаю C ++ ...

zeroDivisible
источник
14
Мое предположение: плохой программист винит стандартные библиотеки в низкой производительности. Вроде как всегда юмористический крик «Я думаю, что нашел ошибку в GCC».
Джон Кугельман,
11
@eclipse: проблемы ACM, над которыми я работал для соревнований, имеют значительный объем ввода / вывода, и ваша программа должна решать вопросы менее чем за 60 секунд ... здесь это становится настоящей проблемой.
mpen
19
--- при этом сказано, что если вам нужно полагаться на scanf () для дополнительного повышения производительности, вы решите проблему неправильно :)
mpen
4
Просто в качестве наблюдения - я поигрался с ним, и со вторыми проблемами (PRIME1) - используя один и тот же алгоритм, оба раза, один раз с использованием cin / cout и один раз с scanf / printf, и первая версия была быстрее, чем вторая (но достаточно близко, чтобы не иметь статистической значимости). Это одна из проблем, которая отмечена как интенсивная по вводу / выводу, и метод ввода / вывода не имел статистической разницы.
Eclipse,
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

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

Итак: разница в скорости действительно есть.


РЕДАКТИРОВАТЬ: Пользователь clyfish указывает ниже, что разница в скорости в значительной степени связана с функциями ввода-вывода iostream, поддерживающими синхронизацию с функциями 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. Если мы не смешиваем stdio и iostream, мы можем отключить его, и тогда iostream будет самым быстрым.

Код: https://gist.github.com/3845568

nibot
источник
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 ++, просто используйте строку ниже перед любыми операциями ввода-вывода.

std::ios::sync_with_stdio(false);

Дополнительные сведения об этом см. В соответствующей документации libstdc ++ .

clyfish
источник
Только что проверил строку выше (std :: ios :: sync_with_stdio (false);) И он действительно делает iostream почти таким же быстрым, как cstdio
gabrielhidasy
также используйте cin.tie (static_cast <ostream *> (0)); для лучшей производительности
Мохамед Эль-Накиб
42

Вероятно, scanf работает несколько быстрее, чем потоки. Хотя потоки обеспечивают большую безопасность типов и не должны анализировать строки формата во время выполнения, обычно они имеют преимущество в том, что они не требуют чрезмерного выделения памяти (это зависит от вашего компилятора и времени выполнения). Тем не менее, если производительность не является вашей единственной конечной целью и вы находитесь на критическом пути, вам действительно следует отдавать предпочтение более безопасным (более медленным) методам.

Здесь есть очень восхитительная статья, написанная Хербом Саттером « Строковые форматтеры усадебной фермы », в которой подробно рассказывается о производительности струнных форматеров, таких как sscanfи, lexical_castи о том, какие вещи заставляли их работать медленно или быстро. Возможно, это аналогично тому, что может повлиять на производительность между вводом-выводом стиля C и стилем C ++. Основное различие с форматерами, как правило, заключалось в безопасности типов и количестве выделений памяти.

1800 ИНФОРМАЦИЯ
источник
19

Я только что провел вечер, работая над проблемой на UVa Online (Factovisors, очень интересная проблема, проверьте):

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 Мэтью Уилсона. библиотеку .

edit - ссылка на публикацию accu в этой библиотеке: http://accu.org/index.php/journals/1539

xtofl
источник
Полностью согласен. Но вы должны знать, что FastFormat предназначен только для вывода. У него нет средств ввода / чтения. (
Во
К сожалению, эта ссылка не работает. Вот копия Wayback Machine: web.archive.org/web/20081222164527/http://fastformat.org
nibot 06
2

Существуют реализации stdio ( libio ), которые реализуют FILE * как streambuf C ++ и fprintf как синтаксический анализатор формата времени выполнения. Потоки ввода-вывода не нуждаются в синтаксическом анализе формата времени выполнения, все это делается во время компиляции. Итак, с общими бэкэндами разумно ожидать, что iostreams будет быстрее во время выполнения.

MSalters
источник
Я так не думаю. Я думаю, что GNU libc - это чистый C и сборка.
Крис Лутц,
2

Да, iostream медленнее, чем 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;
}
pedro.lupin
источник
1
getchar_unlocked () нестандартен и доступен для gcc, а не для визуальной студии
Мохамед Эль-Накиб
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()over, cinесли пишете программное обеспечение на C ++, потому что это нужно cinдля. Если вам нужна производительность, вы, вероятно, все равно не будете писать ввод-вывод на C ++.

dreamlax
источник
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 секунды.

hexec
источник
-1

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

Но, по мнению онлайн-судьи, вы не разрабатываете программное обеспечение, вы создаете программу, которая должна уметь делать то, что ПО Microsoft выполняет за 60 секунд за 3 секунды !!!

Итак, в этом случае золотое правило выглядит так (конечно, если у вас не возникнет еще больше проблем с использованием java)

  • Используйте c ++ и используйте всю его мощность (и тяжесть / медлительность) для решения проблемы.
  • Если вы ограничены во времени, измените cins и couts для printfs и scanfs (если вы запутались, используя строку класса, напечатайте так: printf (% s, mystr.c_str ());
  • Если время по-прежнему ограничено, попробуйте внести очевидные оптимизации (например, избегайте слишком большого количества встроенных функций для / while / dowhiles или рекурсивных функций). Также не забудьте передать слишком большие ссылочные объекты ...
  • Если время по-прежнему ограничено, попробуйте изменить std :: vectors и наборы для c-массивов.
  • Если у вас все еще ограничено время, переходите к следующей задаче ...
Карлос Пачеко
источник
-2

Даже если бы они scanfбыли быстрее cin, это не имело бы значения. В большинстве случаев вы будете читать с жесткого диска или клавиатуры. Получение исходных данных в приложение занимает порядки больше времени , чем требуется , scanfили cinобрабатывать его.

Джей Конрод
источник
А как насчет IPC через трубы? Как вы думаете, там будет заметный скачок в производительности?
dreamlax
Даже при использовании IPC через каналы гораздо больше времени тратится на вход и выход из ядра, чем просто на его анализ с помощью scanf / cin.
Jay Conrod,
8
Я провел тесты в этой области, и, конечно же, производительность отстойная. Хотя для пользовательского ввода это ничтожно мало, это, конечно, не так для вещей, где производительность имеет значение. Однако существуют другие фреймворки C ++, которые работают быстрее.
Йоханнес Шауб - лит,
Проблема в том, что iostream он медленнее чем hdd. Да, это отстой.
polkovnikov.ph