我们有一个包含许多打印消息的守护程序。由于我们正在使用CPU和其他约束性硬件较弱的嵌入式设备进行工作,因此我们希望在最终版本中最大程度地减少printf消息的各种成本(IO,CPU等)。 (用户没有控制台)
我和我的队友有分歧。他认为我们可以将所有内容重定向到/ dev / null。它不会花费任何IO,因此影响将降至最低。但是我认为仍然会占用CPU,我们最好为printf定义一个宏,以便我们可以重写“ printf”(也许只是返回)。
所以我需要一些关于谁是正确的观点。 Linux是否足够聪明以优化printf?我真的对此表示怀疑。
答案 0 :(得分:70)
差不多。
将程序的标准输出重定向到/dev/null
时,对printf(3)
的任何调用仍将评估所有参数,并且字符串格式化过程仍将在调用write(2)
之前进行,它将完整格式的字符串写入流程的标准输出。在内核级别,数据没有写入磁盘,而是被与特殊设备/dev/null
关联的处理程序丢弃。
因此,最好的情况是,您不会绕过或避免评估参数并将参数传递给printf
,printf
之后的字符串格式化作业以及至少一个系统调用的开销。仅通过将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
的格式字符串。 (如果需要这些副作用,可以将snprintf
与NULL
和0
缓冲区一起使用,但是节省下来的钱不太可能偿还所投入的精力。)
答案 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 方法 - 您不需要。