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

时间:2019-01-15 09:40:05

标签: c linux performance embedded dev-null

我们有一个包含许多打印消息的守护程序。由于我们正在使用CPU和其他约束性硬件较弱的嵌入式设备进行工作,因此我们希望在最终版本中最大程度地减少printf消息的各种成本(IO,CPU等)。 (用户没有控制台)

我和我的队友有分歧。他认为我们可以将所有内容重定向到/ dev / null。它不会花费任何IO,因此影响将降至最低。但是我认为仍然会占用CPU,我们最好为printf定义一个宏,以便我们可以重写“ printf”(也许只是返回)。

所以我需要一些关于谁是正确的观点。 Linux是否足够聪明以优化printf?我真的对此表示怀疑。

6 个答案:

答案 0 :(得分:70)

差不多。

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

因此,最好的情况是,您不会绕过或避免评估参数并将参数传递给printfprintf之后的字符串格式化作业以及至少一个系统调用的开销。仅通过将stdout重定向到/dev/null即可实际写入数据。好吧,这是Linux上的真正区别。该实现仅返回要写入的字节数(由调用write(2)的第3个参数指定),而忽略其他所有内容(请参见this answer)。根据所写入的数据量以及目标设备(磁盘或终端)的速度,性能差异可能会相差很大。一般来说,在嵌入式系统上,通过重定向到/dev/null来中断磁盘写操作可以节省大量系统资源,以节省大量写入数据。

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

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

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

答案 1 :(得分:41)

printf函数写入stdout。它不符合针对/dev/null进行优化的要求。 因此,您将需要分析格式字符串和评估任何必要的参数的开销,并且您将至少有一个syscall,并且您还将缓冲区复制到内核地址空间(与syscall的开销相比,这是微不足道的)

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

  

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

     

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

     

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

答案 2 :(得分:10)

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

答案 3 :(得分:5)

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

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

答案 4 :(得分:4)

通常来说,如果实现不影响程序的可观察(功能)输出,则允许执行此类优化。对于printf(),这意味着如果程序不使用返回值,并且没有%n转换,则该实现将不做任何事情。

实际上,我不知道目前(2019年初)在Linux上执行过任何优化的实现-我熟悉的编译器和库将格式化输出并将结果写入null设备,具体取决于在内核上'忽略它。

如果在不使用输出时确实需要节省格式化成本,则可能需要编写自己的转发功能-您将希望它返回void,并且应检查%n的格式字符串。 (如果需要这些副作用,可以将snprintfNULL0缓冲区一起使用,但是节省下来的钱不太可能偿还所投入的精力。)

答案 5 :(得分:0)

在 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;; 以便解析出来。

您会收到未使用的变量警告,但它会做您想做的事情,而且这种副作用甚至会有所帮助,因为它会告诉您有关版本中不需要的计算。

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