即使我将输出重定向到 /dev/null,printf 仍然会有成本吗?

Will printf still have a cost even if I redirect output to /dev/null?

我们有一个包含大量打印消息的守护进程。由于我们正在使用弱 CPU 和其他约束硬件的嵌入式设备,因此我们希望在最终版本中最小化 printf 消息的任何类型的成本(IO、CPU 等)。 (用户没有控制台)

我和我的队友有分歧。他认为我们可以将所有内容重定向到 /dev/null。它不会花费任何 IO,因此影响很小。但我认为它仍然会花费 CPU,我们最好为 printf 定义一个宏,这样我们就可以重写 "printf"(也许只是 return)。

所以我需要一些关于谁是对的意见。 Linux 是否足够聪明来优化 printf?我真的很怀疑。

printf 函数写入 stdout。如果连接到 stdout 的文件描述符被重定向到 /dev/null 那么任何地方都不会写入任何输出(但它仍然会被写入),但是对 printf 本身的调用及其格式化还是会发生。

差不多。

当您将程序的标准输出重定向到 /dev/null 时,对 printf(3) 的任何调用仍将评估所有参数,并且字符串格式化过程仍将在调用 [=12= 之前进行]],它将完整格式化的字符串写入进程的标准输出。在内核级别,数据没有写入磁盘,而是被与特殊设备关联的处理程序丢弃 /dev/null.

所以在最好的情况下,您不会绕过或规避评估参数并将它们传递给 printfprintf 背后的字符串格式化作业以及至少一个系统的开销调用实际写入数据,只需将标准输出重定向到 /dev/null。嗯,这是 Linux 上的真正区别。该实现只是 returns 您想要写入的字节数(由调用 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() 的情况下,这意味着如果程序不使用 return 值,并且如果没有 %n 转换,则允许执行不执行任何操作.

实际上,我不知道 Linux 目前(2019 年初)执行此类优化的任何实现 - 我熟悉的编译器和库将格式化输出并写入结果到空设备,依赖内核'忽略它。

如果你真的需要在不使用输出时节省格式化成本,你可能想要自己编写一个转发函数 - 你会想要它 return void ,您应该检查 %n 的格式字符串。 (如果您需要这些副作用,您可以将 snprintfNULL0 缓冲区一起使用,但节省的费用不太可能偿还投入的努力)。

printf函数写入stdout。它不符合 /dev/null 的优化要求。 因此,您将有解析格式字符串和评估任何必要参数的开销,并且您将有至少一个系统调用,而且您将复制一个缓冲区到内核地址 space(与系统调用可以忽略不计)。

此答案基于POSIX的具体文档。

System Interfaces
dprintf, fprintf, printf, snprintf, sprintf - print formatted output

The fprintf() function shall place output on the named output stream. The printf() function shall place output on the standard output stream stdout. The sprintf() function shall place output followed by the null byte, '[=13=]', in consecutive bytes starting at *s; it is the user's responsibility to ensure that enough space is available.

Base Definitions
shall
For an implementation that conforms to POSIX.1-2017, describes a feature or behavior that is mandatory. An application can rely on the existence of the feature or behavior.

使用 printf() 源代码作为指南编写您自己的包装 printf() 的代码,如果设置了 noprint 标志则立即返回。这样做的缺点是实际打印时会消耗更多资源,因为必须解析格式字符串两次。但它在不打印时使用的资源可以忽略不计。不能简单地替换 printf(),因为 printf() 内部的底层调用可以随着 stdio 库的更新版本而改变。

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

在C中写0;确实执行但什么都不执行,这类似于;

意味着你可以写一个像

这样的宏
#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;; 以便它被解析出来。

你会收到 Unused variable 警告,但它会做你想做的,这个副作用甚至会很有帮助,因为它会告诉你在你的版本中不需要的计算。

.UTF8String 是一种将 NSStrings 转换为 const char* 的 Objective-C 方法 - 你不需要。