Cin очистка буфера

Снова про ввод/вывод в C++

Cin очистка буфера

Всем привет!

Хочу продолжить тему скорости ввода/вывода в C++, которую когда-то начал товарищ freopen. freopen сравнивал производительность двух способов ввода/вывода в C++: унаследованной от C библиотеки stdio () и более новой библиотеки iostreams (/…).

Однако в этих тестах не было учтено, что iostreams можно значительно ускорить, включив некоторые оптимизации. Об их существовании уже неоднократно упоминалось на Codeforces (раз, два, три).

Сейчас я написал софт, который сравнивает производительность stdio и iostreams на стандартном вводе/выводе с учётом этого.  

UPD1: Добавлена информация про _CRT_DISABLE_PERFCRIT_LOCKS.
UPD2: Для полноты картины добавлены вызовы printf()/scanf() на строках.  

Что это за оптимизации?

Первая состоит в том, что в начале программы, перед каким-либо вводом/выводом, можно вставить строчку

ios_base::sync_with_stdio(false);

Эта команда отключает синхронизацию iostreams с stdio (описание). По умолчанию она включена, то есть, iostreams и stdio можно использовать вместе на одном и том же потоке, перемежая их вызовы. После отключения синхронизации так делать больше нельзя, однако за счёт этого iostreams может работать быстрее.

Вторая оптимизация заключается в том, что для cin можно выключить привязку к cout:

cin.tie(NULL);По умолчанию cin привязан к cout, что означает, что перед каждой операцией над cin сначала сбрасывается буфер cout (описание). Отключение этой функции опять-таки позволяет iostreams работать быстрее. С этой оптимизацией надо быть внимательным в интерактивных задачах: либо не использовать её, либо не забывать явный flush.

Отмечу ещё, что на производительность iostreams негативно влияет частое использование endl, поскольку endl не только выводит символ новой строки, но и сбрасывает буфер потока (описание). Вместо endl можно просто выводить '' или “”.

Какие тесты включены в программу?

Я постарался сымитировать наиболее типичные ситуации, возникающие при решении задач.

  • Ввод/вывод int с помощью stdio, iostreams и, для сравнения, самопальных функций
  • Ввод/вывод double
  • Посимвольный ввод/вывод
  • Ввод/вывод строк: как с char *, так и с std::string

Какие тесты не включены в программу?

  • long long — полагаю, результат будет коррелировать с int
  • Преобразование int и запись результата в собственный буфер достаточно большого размера, а затем прямой вывод буфера с помощью fwrite()/cout.write() (и аналогичное решение для ввода)
  • Экзотический способ посимвольного ввода/вывода: cin.rdbuf()->sgetc() и cout.rdbuf()->sputc()
  • Какие-либо манипуляции с размером буфера потоков (похоже, что в GCC iostreams для стандартных потоков игнорирует пользовательские установки насчёт этого). Это направление ещё можно исследовать потом.

Как это запустить у себя?

Надо скомпилировать программу, не забыв включить оптимизацию (-O2/Release), и запустить её с тем же рабочим каталогом, где она и находится. Если при запуске в Windows появляется сообщение Access denied, то может помочь запуск программы с повышенными правами. Программе потребуется пара сотен мегабайтов свободного места в каталоге для временных файлов.

Дополнительные замечания

  • Почему нужно для каждого теста создавать новый процесс?

Из-за ios_base::sync_with_stdio(false), который препятствует поочерёдному тестированию stdio и iostreams, а также (теоретически) мешает использовать freopen(), чтобы перенаправлять cin/cout.

  • Зачем удалять файл от предыдущего запуска перед каждым тестом?

Чтобы все запуски были в равных условиях. Хотя, это несколько спорный вопрос. Может, лучше не удалять, а переписывать?

  • Почему время измеряет запускаемый, а не запускающий процесс?

Чтобы исключить время запуска/завершения процесса.

  • Почему бы вместо clock() не использовать более точные вызовы, например, getrusage()?

Это можно. Но сначала мне надо понять, как это делать в Windows 🙂

Результаты

Запускал на компьютере с Pentium 4, так что время может показаться несколько большим.

  • Для Visual C++ 2010: http://pastie.org/4680309

int, printf 9.45 9.48 9.44int, cout 22.03 22.01 22.21int, custom/out 11.17 11.06 11.20int, scanf 5.04 4.77 4.82int, cin 20.26 20.16 20.16int, custom/in 10.25 10.25 10.25double, printf 19.23 18.98 18.95double, cout 37.49 37.52 37.44double, scanf 12.11 11.75 11.73double, cin 26.88 26.57 26.57char, putchar 13.29 13.76 13.48char, cout 23.52 24.15 23.41char, getchar 12.87 12.82 12.74char, cin 16.13 16.22 16.50char *, printf 6.88 6.74 6.57char *, puts 3.95 3.82 3.95char *, cout 6.36 6.32 6.43string, cout 6.40 6.40 6.61char *, scanf 6.16 6.10 6.13char *, gets 3.98 3.96 3.96char *, cin 8.72 8.91 8.85string, getline 11.70 11.47 11.53

Здесь всё очевидно: stdio значительно превосходит по скорости iostreams. Примечательно, что на int printf()/scanf() быстрее даже самописных функций (однако см. дополнение ниже).

Ввод/вывод строк с помощью puts()/gets() быстрее, чем с помощью printf()/scanf() — ну, это и понятно.

Запись std::string занимает столько же, сколько и запись char *, а вот чтение в std::string медленнее — наверняка из-за необходимости динамически выделять память.

  • Для MinGW (GCC 4.7.0): http://pastie.org/4680314

int, printf 9.72 9.61 9.61int, cout 6.08 6.05 6.10int, custom/out 2.73 2.75 2.76int, scanf 5.01 5.01 5.01int, cin 3.99 4.04 4.04int, custom/in 0.86 0.86 0.87double, printf 22.51 22.40 22.42double, cout 110.98 111.77 111.01double, scanf 12.18 12.20 12.17double, cin 118.87 118.84 118.87char, putchar 1.67 1.65 1.64char, cout 3.93 3.87 3.85char, getchar 0.78 0.80 0.80char, cin 3.29 3.31 3.29char *, printf 5.55 5.47 5.49char *, puts 5.37 5.32 5.41char *, cout 8.72 8.72 8.78string, cout 8.74 8.71 9.06char *, scanf 7.07 7.04 7.02char *, gets 3.84 3.79 3.77char *, cin 5.30 5.38 5.35string, getline 14.15 14.12 14.16

А здесь всё уже не так однозначно. Неожиданно оказывается, что iostreams на 20-30% быстрее stdio на int. Правда, самописные функции значительно обгоняют и то, и то. С double наоборот: iostreams заметно тормозит.

Посимвольный ввод/вывод с putchar()/getchar() работает в 2-3 раза быстрее cout/cin. Ввод/вывод строк отличается не так сильно, но stdio и тут быстрее. На строках puts()/gets() также быстрее, чем printf()/scanf().

std::string, как и в предыдущем случае, работает одинаково с char * на выводе, однако медленнее на вводе.

Источник: https://codeforces.com/blog/entry/5217?locale=ru

Обзор средств ввода-вывода в C++

Cin очистка буфера

Приложение, написанное на любом языке программирования, должно взаимодействовать с окружающим миром. Иначе пользы от него не будет. Как правило, такое взаимодействие осуществляется посредством ввода-вывода информации на монитор или в файл.

Правда, есть некоторое множество программ, которые не используют файловый или консольный ввод-вывод: это программы, осуществляющие низкоуровневое взаимодействие с аппаратной частью компьютера и периферией (ядро ОС, драйверы и пр.

), но это уже экзотика.

В стандартном C++ существует два основных пути ввода-вывода информации: с помощью потоков, реализованных в STL (Standard Template Library) и посредством традиционной системы ввода-вывода, унаследованной от C. Если копнуть немного глубже, то окажется, что и потоки, и традиционная система ввода-вывода для осуществления необходимых действий используют вызовы операционной системы. И это правильно.

Дальнейшее изложение не претендует на полноту, но описывает основные принципы использования библиотек. Подробности использования можно посмотреть в многочисленной литературе по C++ и STL, в MSDN и пр.

Традиционный ввод-вывод

Для использования традиционного ввода-вывода в духе C, в программу необходимо включить заголовочный файл . (Разумеется, компилятор должен иметь доступ к соответствующей объектной библиотеке для правильной сборки исполняемого файла.)

Библиотека stdio предоставляет необходимый набор функций для ввода и вывода информации как в текстовом, так и в двоичном представлении.

Следует отметить, что в отличие от классической C‑библиотеки, в современных библиотеках имеются более безопасные аналоги «классических» функций.

Как правило, они имеют такое же имя, к которому добавлен суффикс _s. Рекомендуется использовать именно эти, безопасные функции.

Самая Первая Программа с использованием библиотеки stdio выглядит так:

#include int main(){ printf(“Hello, world!”);}

При запуске консольного приложения неявно открываются три потока: stdin — для ввода с клавиатуры, stdout — для буферизованного вывода на монитор и stderr — для небуферизованного вывода на монитор сообщений об ошибках. Эти три символа определены посредством .

В stdio для консольного ввода-вывода предусмотрена отдельная группа функций. Однако эти функции, как правило, являются обёртками для аналогичных функций файлового ввода-вывода, для которых аргумент типа FILE задан по умолчанию.

Самая Первая Программа с использование файлового вывода из библиотеки stdio выглядит так:

#include int main(){ fprintf(stdout, “Hello, world!”);}

Некоторые популярные функции из stdio:

FILE *fopen(const char *filename, const char *mode) // открытие файлаint fclose(FILE *stream) // закрытие файла int printf(const char *format, …) // фоматированный консольный выводint fprintf(FILE *stream, const char *formatб, …) // форматированный ввод из файлаint sprintf(char *s, const char *format, …) // форматированный вывод в буфер (строку) int scanf(const char *format, …) // фоматированный консольный вводint fscanf(FILE *stream, const char *format, …) // фоматированный вводint sscanf (const char *s, const char *format, …) // фоматированный ввод из буфера (строки) int fgetc(FILE *stream) // читает символ из файлаchar *fgets(char *s, int n, FILE *stream) // читает строку из файлаint fputc(int с, FILE *stream) // записывает символ в файлint fputs(const char *s, FILE *stream) // записывает строку в файлint getchar(void) // читает символ из stdinchar *gets(char *s) // читает строку из stdinint putchar(int с) // записывает символ в stdoutint puts(const char *s) // записывает строку в stdoutint ungetc(int с, FILE *stream) // возвращает символ обратно в файл для последующего чтения Сущность FILE представляет собой структуру, в которой хранится вся информация для управления потоком ввода-вывода.

Файл открывается функцией fopen(), которой передаются два параметра. Первый параметр определяет имя файла. Второй — определяет режим открытия файла: чтение, запись, произвольный доступ и т.п., а также указание на то, как работать с данными: в текстовом или двоичном режиме. Подробности — см. в документации.

Пример использования stdio

#include #include const char *filename = “testfile.txt”; int main(){ FILE *fin, *fout; int ecode; // открытие файла для записи в текстовом режиме, // запись данных и закрытие файла. if((fout = fopen(filename, “w”)) != NULL) { for (int i = 0; i < 16; i++) { if ((ecode = fprintf(fout, "%d", i*i))

Источник: https://code-live.ru/post/cpp-input-output/

Поделиться:
Нет комментариев

    Добавить комментарий

    Ваш e-mail не будет опубликован. Все поля обязательны для заполнения.