Будет ли printf по-прежнему иметь стоимость, даже если я перенаправлю вывод в /dev/null?

У нас есть демон, который содержит много печатных сообщений. Поскольку мы работаем над встроенным устройством со слабым процессором и другим ограничивающим оборудованием, мы хотим минимизировать любые затраты (ввод-вывод, процессор и т.д.) Сообщений printf в нашей окончательной версии. (У пользователей нет консоли)

Мой товарищ по команде и у меня есть разногласия. Он думает, что мы можем просто перенаправить все в /dev/null. Это не будет стоить IO, поэтому привязанность будет минимальной. Но я думаю, что это все равно будет стоить CPU, и мы лучше определим макрос для printf, чтобы мы могли переписать "printf" (возможно, просто вернуть).

Поэтому мне нужны некоторые мнения о том, кто прав. Будет ли Linux достаточно умным, чтобы оптимизировать printf? Я действительно сомневаюсь в этом.

Ответы

Ответ 1

Довольно много.

Когда вы перенаправляете стандартный вывод программы в /dev/null, любой вызов printf(3) будет по-прежнему оценивать все аргументы, и процесс форматирования строки будет происходить до вызова write(2), который записывает полностью отформатированную строку на стандартный вывод процесса. На уровне ядра данные не записываются на диск, а отбрасываются обработчиком, связанным со специальным устройством /dev/null.

Таким образом, в лучшем случае, вы не будете обходить или уклоняться от затрат на оценку аргументов и их передачу в printf, задание форматирования строки в printf и, по крайней мере, один системный вызов для фактической записи данных, просто перенаправив stdout в /dev/null. Ну, это настоящая разница в Linux. Реализация просто возвращает количество байтов, которое вы хотели записать (указано 3-м аргументом вашего вызова write(2)), и игнорирует все остальное (см. Этот ответ). В зависимости от объема записываемых данных и скорости целевого устройства (диска или терминала) разница в производительности может сильно различаться. Во встроенных системах, вообще говоря, отключение записи на диск путем перенаправления в /dev/null может сэкономить довольно много системных ресурсов для нетривиального объема записанных данных.

Хотя теоретически программа может обнаруживать /dev/null и выполнять некоторые оптимизации в рамках ограничений стандартов, которым они соответствуют (ISO C и POSIX), основываясь на общем понимании общих реализаций, они практически этого не делают (т.е. я не знаю о любая система Unix или Linux делает это).

Стандарт POSIX предписывает запись в стандартный вывод для любого вызова printf(3), поэтому он не соответствует стандарту для подавления вызова write(2) зависимости от связанных файловых дескрипторов. Более подробную информацию о требованиях POSIX вы можете прочитать в ответе Дэймона. Да, и небольшое замечание: все дистрибутивы Linux практически POSIX-совместимы, несмотря на то, что они не сертифицированы.

Имейте в виду, что если вы полностью замените printf, некоторые побочные эффекты могут пойти не так, например, printf("%d%n", a++, &b). Если вам действительно нужно подавить вывод в зависимости от среды выполнения программы, рассмотрите возможность установки глобального флага и оберните printf, чтобы проверить флаг перед печатью - это не приведет к замедлению программы до такой степени, чтобы была видна потеря производительности, так как одиночная проверка условия намного быстрее, чем вызов printf и выполнение всего форматирования строки.

Ответ 2

printf функция будет писать в stdout. Не соответствует оптимизации для /dev/null. Следовательно, у вас будут дополнительные затраты на анализ строки формата и оценку любых необходимых аргументов, и у вас будет хотя бы один системный вызов, плюс вы скопируете буфер в адресное пространство ядра (что, по сравнению со стоимостью системного вызова, пренебрежимо мало).,

Этот ответ основан на конкретной документации POSIX.

Системные интерфейсы
dprintf, fprintf, printf, snprintf, sprintf - вывод на печать в формате

Функция fprintf() помещает вывод в именованный выходной поток. Функция printf() помещает вывод в стандартный поток вывода stdout. Функция sprintf() помещает вывод, за которым следует нулевой байт '\ 0', в последовательные байты, начинающиеся с * s; ответственность за обеспечение достаточного пространства лежит на пользователе.

Базовые определения
должен
Для реализации, соответствующей POSIX.1-2017, описывается функция или поведение, которые являются обязательными. Приложение может полагаться на существование функции или поведения.

Ответ 3

Функция printf пишет в стандартный stdout. Если дескриптор файла, подключенный к stdout, перенаправляется в /dev/null то нигде не будет записано никакого вывода (но оно все равно будет записано), но вызов самого printf и его форматирование все равно произойдут.

Ответ 4

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

На практике мне неизвестно о какой-либо реализации в Linux, которая в настоящее время (в начале 2019 г.) выполняет такую оптимизацию - компиляторы и библиотеки, с которыми я знаком, отформатируют вывод и запишут результат на нулевое устройство, опираясь на ядро. игнорировать это.

Возможно, вы захотите написать собственную функцию пересылки, если вам действительно нужно сэкономить на стоимости форматирования, когда вывод не используется - вам нужно вернуть void, и вы должны проверить строку форматирования для %n. (Вы можете использовать snprintf с буфером NULL и 0 если вам нужны эти побочные эффекты, но экономия вряд ли окупит вложенные усилия).

Ответ 5

Напишите свой собственный, который оборачивает printf(), используя источник printf() в качестве ориентира, и возвращая его немедленно, если установлен флаг noprint. Недостатком этого является то, что при фактической печати он потребляет больше ресурсов из-за необходимости разбора строки формата дважды. Но он использует незначительные ресурсы, когда не печатает. Невозможно просто заменить printf(), потому что базовые вызовы внутри printf() могут измениться на более новую версию библиотеки stdio.

void printf2 (const char * formattring,...);