Будет ли printf по-прежнему иметь стоимость, даже если я перенаправлю вывод в /dev/null?
У нас есть демон, который содержит много сообщений для печати. Поскольку мы работаем над встроенным устройством со слабым процессором и другим ограничивающим оборудованием, мы хотим минимизировать любые затраты (ввод-вывод, процессор и т. Д.) Сообщений printf в нашей окончательной версии. (У пользователей нет консоли)
Мой товарищ по команде и у меня есть разногласия. Он думает, что мы можем просто перенаправить все в / dev / null. Это не будет стоить IO, поэтому привязанность будет минимальной. Но я думаю, что это все равно будет стоить CPU, и мы лучше определим макрос для printf, чтобы мы могли переписать "printf" (возможно, просто вернуть).
Поэтому мне нужны некоторые мнения о том, кто прав. Будет ли Linux достаточно умным, чтобы оптимизировать printf? Я действительно сомневаюсь в этом.
6 ответов
Довольно много.
Когда вы перенаправляете стандартный вывод программы на /dev/null
любой вызов printf(3)
все равно оценит все аргументы, и процесс форматирования строки все равно будет происходить перед вызовом write(2)
, который записывает полностью отформатированную строку в стандартный вывод процесса. На уровне ядра данные не записываются на диск, а отбрасываются обработчиком, связанным со специальным устройством. /dev/null
,
Таким образом, в лучшем случае вы не будете обходить или уклоняться от накладных расходов по оценке аргументов и передаче их printf
, задание форматирования строки позади printf
и, по крайней мере, один системный вызов для фактической записи данных, просто перенаправив стандартный вывод на /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
и делает все форматирование строки.
printf
функция напишет в stdout
, Не соответствует оптимизации для /dev/null
, Следовательно, у вас будут дополнительные затраты на анализ строки формата и оценку любых необходимых аргументов, и у вас будет хотя бы один системный вызов, плюс вы скопируете буфер в адресное пространство ядра (что, по сравнению со стоимостью системного вызова, пренебрежимо мало).,
Этот ответ основан на конкретной документации POSIX.
Системные интерфейсы
dprintf, fprintf, printf, snprintf, sprintf - вывод на печать в форматеФункция fprintf() помещает вывод в именованный поток вывода. Функция printf () помещает вывод в стандартный поток вывода stdout. Функция sprintf() помещает вывод, за которым следует нулевой байт '\0', в последовательные байты, начинающиеся с *s; это ответственность пользователя, чтобы гарантировать, что достаточно места доступно.
Базовые определения
должен
Для реализации, которая соответствует POSIX.1-2017, описывает обязательную функцию или поведение. Приложение может полагаться на существование функции или поведения.
printf
функция пишет в stdout
, Если дескриптор файла подключен к stdout
перенаправлен на /dev/null
тогда нигде не будет записан вывод (но он все равно будет записан), но вызов printf
само по себе и его форматирование все равно произойдет.
Напишите свой собственный, который оборачивает printf(), используя источник printf() в качестве ориентира, и возвращая немедленно, если установлен флаг noprint. Недостатком этого является то, что при фактической печати он потребляет больше ресурсов из-за необходимости дважды анализировать строку формата. Но он использует незначительные ресурсы, когда не печатает. Невозможно просто заменить printf(), потому что базовые вызовы внутри printf() могут измениться на более новую версию библиотеки stdio.
void printf2 (const char * formattring, ...);
Вообще говоря, реализации разрешается выполнять такие оптимизации, если они не влияют на наблюдаемые (функциональные) результаты программы. В случае printf()
, это будет означать, что если программа не использует возвращаемое значение, и если нет %n
преобразования, то реализация будет позволено ничего не делать.
На практике мне неизвестно о какой-либо реализации в Linux, которая в настоящее время (в начале 2019 года) выполняет такую оптимизацию - компиляторы и библиотеки, с которыми я знаком, отформатируют вывод и запишут результат на нулевое устройство, опираясь на ядро. игнорировать это.
Возможно, вы захотите написать собственную функцию пересылки, если вам действительно нужно сэкономить на стоимости форматирования, когда вывод не используется - вы захотите, чтобы он возвратил void
и вы должны проверить строку формата для %n
, (Вы могли бы использовать snprintf
с NULL
а также 0
буфер, если вам нужны эти побочные эффекты, но экономия вряд ли окупит вложенные усилия).
в C написание не выполняет и ничего, что похоже на.
означает, что вы можете написать макрос вроде
#if DEBUG
#define devlognix(frmt,...) fprintf(stderr,(frmt).UTF8String,##__VA_ARGS__)
#else
#define nadanix 0
#define devlognix(frmt,...) nadanix
#endif
#define XYZKitLogError(frmt, ...) devlognix(frmt)
где
XYZKitLogError
будет вашей командой журнала. или даже
#define nadanix ;
который удалит все вызовы журнала во время компиляции и заменит на
0;
или же
;
так что он разбирается.
вы получите предупреждения о неиспользуемых переменных , но он делает то, что вы хотите, и этот побочный эффект может быть даже полезен, потому что он сообщает вам о вычислениях, которые не нужны в вашем выпуске.
.UTF8String - это метод Objective-C, преобразующий NSStrings в const char * - вам не нужно.