61

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

我和我的队友意见不合。他认为我们可以将所有内容重定向到 /dev/null。它不会花费任何 IO,因此情感将是最小的。但我认为它仍然会花费 CPU,我们最好为 printf 定义一个宏,这样我们就可以重写“printf”(也许只是返回)。

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

4

6 回答 6

72

差不多。

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

所以在最好的情况下,您不会绕过或回避评估参数并将它们传递给 的开销,printf后面的字符串格式化作业printf,以及至少一个系统调用来实际写入数据,只需将 stdout 重定向到/dev/null. 嗯,这在 Linux 上是一个真正的区别。该实现只返回您想要写入的字节数(由您调用的第三个参数指定write(2))并忽略其他所有内容(请参阅此答案)。根据您正在写入的数据量以及目标设备(磁盘或终端)的速度,性能差异可能会有很大差异。在嵌入式系统上,一般来说,通过重定向到/dev/null可以为大量的写入数据节省相当多的系统资源。

尽管在理论上,该程序可以/dev/null在它们遵守的标准(ISO C 和 POSIX)的限制范围内检测并执行一些优化,但基于对常见实现的一般理解,它们实际上不会(即我不知道任何 Unix 或 Linux系统这样做)。

POSIX 标准要求将任何对 的调用写入标准输出printf(3),因此write(2)根据关联的文件描述符抑制对 的调用是不符合标准的。有关 POSIX 要求的更多详细信息,您可以阅读Damon 的回答。哦,还有一个简短的说明:所有 Linux 发行版实际上都符合 POSIX 标准,尽管没有经过认证

请注意,如果您printf完全更换,一些副作用可能会出错,例如printf("%d%n", a++, &b). 如果您确实需要根据程序执行环境抑制输出,请考虑设置一个全局标志并包装 printf 以在打印前检查该标志 - 它不会使程序减慢到性能损失可见的程度,因为单个条件检查比调用和执行所有字符串格式化要快得多。printf

于 2019-01-15T09:46:13.477 回答
41

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

此答案基于 POSIX 的特定文档。

系统接口
dprintf、fprintf、printf、snprintf、sprintf - 打印格式化输出

fprintf() 函数应将输出放在指定的输出流上。printf() 函数应将输出放在标准输出流 stdout 上。sprintf() 函数应将输出后跟空字节“\0”放在从 *s 开始的连续字节中;确保有足够的可用空间是用户的责任。

基本定义

对于符合 POSIX.1-2017 的实现,描述强制性的功能或行为。应用程序可以依赖于功能或行为的存在。

于 2019-01-15T13:17:42.410 回答
11

printf函数写入stdout. 如果连接到的文件描述符stdout被重定向到,/dev/null则不会在任何地方写入任何输出(但仍会写入),但对printf自身的调用和它所做的格式化仍然会发生。

于 2019-01-15T09:42:11.380 回答
5

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

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

于 2019-01-15T17:43:56.607 回答
4

一般而言,如果它们不影响程序的可观察(功能)输出,则允许实现执行此类优化。在 的情况下printf(),这意味着如果程序不使用返回值,并且如果没有%n转换,那么实现将被允许不做任何事情。

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

如果您确实需要在不使用输出时节省格式化成本,您可能需要编写自己的转发函数 - 您希望它返回void,并且您应该检查格式字符串以获取%n. (如果您需要这些副作用,您可以使用snprintfaNULL和缓冲区,但节省的费用不太可能偿还所投入的努力)。0

于 2019-01-15T13:15:24.513 回答
1

在 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您的 Log 命令在哪里。甚至

#define nadanix ;

这将在编译时踢出所有日志调用并替换为0;or;所以它被解析出来。

您将收到未使用的变量警告,但它会执行您想要的操作,而且这种副作用甚至会很有帮助,因为它会告诉您发布中不需要的计算。

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

于 2021-06-15T06:04:21.487 回答