printf замедляет мою программу - PullRequest
26 голосов
/ 02 декабря 2009

У меня есть небольшая C-программа для вычисления хешей (для хеш-таблиц). Надеюсь, код выглядит достаточно чистым, но есть кое-что не связанное с ним, что меня беспокоит.

Я могу легко сгенерировать около миллиона хэшей за 0,2-0,3 секунды (с помощью / usr / bin / time). Однако, когда я printf () вставляю их в цикл for, программа замедляется примерно до 5 секунд.

  1. Почему это?
  2. Как сделать это быстрее? Может быть, mmapp () в стандартный вывод?
  3. Как в связи с этим разработан stdlibc и как его можно улучшить?
  4. Как ядро ​​могло бы поддерживать его лучше? Как его нужно изменить, чтобы сделать пропускную способность локальных «файлов» (сокетов, каналов и т. Д.) Действительно быстрой?

Я с нетерпением жду интересных и подробных ответов. Спасибо.

PS: это набор инструментов для компиляции, так что не стесняйтесь вдаваться в детали. Хотя это не имеет ничего общего с самой проблемой, я просто хотел указать, что детали меня интересуют.

Добавление

Я ищу более программные подходы к решениям и объяснениям. В самом деле, трубопровод выполняет свою работу, но я не могу контролировать то, что делает «пользователь».

Конечно, я сейчас провожу тестирование, которое не будет сделано "обычными пользователями". НО это не меняет того факта, что простое printf () замедляет процесс, и это проблема, для которой я пытаюсь найти оптимальное программное решение.


Приложение - Потрясающие результаты

Базовое время указано для простых вызовов printf () внутри TTY и занимает около 4 минут 20 секунд.

Тестирование в / dev / pts (например, Konsole) ускоряет вывод примерно до 5 секунд.

При использовании setbuffer () в моем тестовом коде требуется примерно столько же времени для размера 16384, почти то же самое для 8192: около 6 секунд.

setbuffer () не имеет очевидно эффекта при его использовании: это занимает столько же времени (на TTY около 4 минут, на PTS около 5 секунд).

Удивительно, что , если я запускаю тест на TTY1, а затем переключаюсь на другой TTY , это займет точно так же, как на PTS: около 5 секунд .

Заключение : ядро ​​делает что-то, что связано с доступностью и удобством пользователя. Да!

Обычно он должен быть одинаково медленным, независимо от того, смотрите ли вы на TTY, пока он активен, или переключаетесь на другой TTY.


Урок : при запуске программ с интенсивным выходом переключайтесь на другой TTY!

Ответы [ 9 ]

31 голосов
/ 02 декабря 2009

Небуферизованный вывод очень медленный.

По умолчанию stdout полностью буферизован, однако при подключении к терминалу stdout либо не буферизуется, ни буферизуется.

Попробуйте включить буферизацию для stdout, используя setvbuf(), например:

char buffer[8192];

setvbuf(stdout, buffer, _IOFBF, sizeof(buffer));
14 голосов
/ 02 декабря 2009

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

При выводе на консоль прокрутка обычно является убийцей.

9 голосов
/ 02 декабря 2009

Если вы используете printf () для консоли, это обычно очень медленно. Я не уверен, почему, но я верю, что он не вернется, пока консоль графически не отобразит выведенную строку. Кроме того, вы не можете mmap () в stdout.

Запись в файл должна быть намного быстрее (но все же на несколько порядков медленнее, чем вычисление хэша, все операции ввода-вывода выполняются медленно).

7 голосов
/ 02 декабря 2009

Вы можете попытаться перенаправить вывод в оболочке из консоли в файл. Благодаря этому журналы размером в гигабайты могут быть созданы за считанные секунды.

6 голосов
/ 02 декабря 2009
  1. I / O всегда медленный по сравнению с прямое вычисление. Система имеет ждать больше компонентов, чтобы быть доступны для того, чтобы использовать их. Это затем должен ждать ответа прежде чем он сможет продолжить. Наоборот если это просто вычисления, то это только действительно движущиеся данные между Регистры ОЗУ и ЦП.

  2. Я не проверял это, но может быть быстрее добавить ваши хеши в строку, а затем просто напечатать строку в конце. Хотя, если вы используете C, а не C ++, это может оказаться проблемой!

Боюсь, что 1010 * 3 и 4 за мной.
4 голосов
/ 02 декабря 2009

Я обнаружил давно , используя эту технику что-то, что должно было быть очевидным. Мало того, что ввод / вывод медленен, особенно для консоли, но форматирование десятичных чисел также не быстрое. Если вы можете поместить числа в двоичном виде в большие буферы и записать их в файл, вы обнаружите, что это намного быстрее.

Кроме того, кто их будет читать? Нет смысла печатать их все в удобочитаемом формате, если никому не нужно читать их все.

4 голосов
/ 02 декабря 2009

Поскольку ввод-вывод всегда намного медленнее, чем вычисление ЦП, вы можете сначала сохранить все значения в максимально быстрых ввода-вывода. Так что используйте RAM, если у вас достаточно, используйте Files, если нет, но это намного медленнее, чем RAM.

Распечатка значений теперь может выполняться позже или параллельно другим потоком. Таким образом, поток (ы) вычисления, возможно, не должны ждать, пока printf вернется.

4 голосов
/ 02 декабря 2009
  1. Почему бы не создавать строки по требованию, а не в точке строительства? Нет смысла выводить 40 экранов данных за одну секунду, как вы можете их прочитать? Почему бы не создать выходные данные по мере необходимости и просто отобразить последний экран полностью, а затем по мере необходимости его прокручивает пользователь ???

  2. Почему бы не использовать sprintf для печати в строку, а затем создать объединенную строку из всех результатов в памяти и напечатать в конце?

  3. Переключившись на sprintf, вы можете четко видеть, сколько времени затрачивается на преобразование формата и сколько потрачено на отображение результата на консоли и соответствующее изменение кода.

  4. Вывод на консоль по определению медленен, создание хэша манипулирует только несколькими байтами памяти. Вывод на консоль должен проходить через многие уровни операционной системы, в которой будет код для обработки блокировки потоков / процессов и т. Д., Как только он в конечном итоге попадет в драйвер дисплея, который может быть устройством со скоростью 9600 бод! или большой растровый дисплей, простые функции, такие как прокрутка экрана, могут включать манипулирование мегабайтами памяти.

2 голосов
/ 02 декабря 2009

Я полагаю, что тип терминала использует некоторые буферизованные операции вывода, поэтому, когда вы выполняете printf, он не выводится за доли микросекунды, он сохраняется в буферной памяти подсистемы терминала.

На это могут повлиять другие вещи, которые могут вызвать замедление, возможно, на нем выполняется операция с интенсивным использованием памяти, отличная от вашей программы. Короче говоря, существует слишком много вещей, которые могут происходить в одно и то же время: подкачка страниц, подкачка, тяжелый ввод-вывод другим процессом, конфигурация используемой памяти, возможно, обновление памяти и т. Д.

Может быть, лучше объединить строки, пока не будет достигнут определенный предел, а затем, когда это так, записать все сразу. Или даже используя pthreads для выполнения желаемого выполнения процесса.

Отредактировано: Что касается 2,3, это за мной. Для 4, Я не знаком с Sun, но знаю и перепутал с Solaris, Возможно, есть опция ядра для использования виртуального tty. Я признаю, что это было давно, так как я возился с ядро конфигурирует и перекомпилирует. В связи с этим у меня может быть не очень хорошая память об этом, имейте рут с опциями, чтобы увидеть.

user@host:/usr/src/linux $ make; make menuconfig **OR kconfig if from X**

Откроется меню ядра, откройте окно настроек видео под поддеревом устройств.

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

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

...