Python-使用.format进行日志记录会产生额外的CPU周期



test.py

import logging
# V1
logging.debug('%s before you %s', 'Look', 'leap!')
# V2
logging.debug('{} before you {}'.format('Look', 'leap!'))

我上面有以下代码。

当我使用python test.py --log=INFO执行时,V2会产生额外的CPU周期,因为它会首先格式化字符串,然后由于调试级别的原因而不输出任何内容。

有没有办法在日志记录中使用.format样式,并且如果日志最终没有输出,就不会产生额外的CPU周期?

编辑:第1部分->是的,这是真的通过这个测试代码(虽然很小)

old-format.py

import logging
# V1
for x in range(0, 100000):
logging.debug('%s before you %s', 'Look', 'leap!')

new-format.py

import logging
# V2
for x in range(0, 100000):
logging.debug('{} before you {}'.format('Look', 'leap!'))

python -m cProfile ~/Desktop/old-format.py->在0.176秒内调用500464个函数

python -m cProfile ~/Desktop/new-format.py->600464函数调用0.237秒

是的,当你这样做时,它必须做额外的工作,创建永远不会使用的字符串。也就是说,一个小黑客可以让你切换到懒散格式的消息。

不过,Python人员提供了切换指南,可以使用带有__str__的包装类,该包装类只有在日志记录实际发生时才会调用,也可以使用LoggerAdapter子类包装器为延迟执行必要的大括号格式的记录器(因此在使用时根本没有额外的kruft)。后一种方法在使用时是最干净的。如何引导的简单示例如下:

import logging
class Message(object):
def __init__(self, fmt, args):
self.fmt = fmt
self.args = args
def __str__(self):
return self.fmt.format(*self.args)
class StyleAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra=None):
super(StyleAdapter, self).__init__(logger, extra or {})
def log(self, level, msg, *args, **kwargs):
if self.isEnabledFor(level):
msg, kwargs = self.process(msg, kwargs)
self.logger._log(level, Message(msg, args), (), **kwargs)
logger = StyleAdapter(logging.getLogger(__name__))
def main():
logger.debug('Hello, {}', 'world!')
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
main()

在实际的代码中,MessageStyleAdapter可能隐藏在一个单独的模块中,在使用它们的模块中只留下很少的自定义代码。

说真的,你担心的是错误的问题。是的,字符串将被格式化,最终可能会被丢弃。不,没关系。

我怎么知道?

  • 你量过吗?我敢打赌你还没有,如果你这样做了,你会发现格式化字符串的时间很短,比写入磁盘的时间短几个数量级,而且接近调用函数所需的时间。

  • 如果日志记录处于一个被调用数千次的紧密循环中,那么您的日志将是丑陋的并且难以使用。所以我敢打赌,当你完成调试时,它不存在,或者不会存在。

  • 如果日志记录不是处于一个紧密的循环中,那么丢失消息的总格式化时间将非常短。

  • 如果程序是CPU密集型的,并且在Python解释器中花费了大量时间,那么日志记录并不是原因。您需要寻找能够减轻解释器工作负担的Python技术/库,或者一种能够减轻解释器工作量的语言。

  • 如果日志模块的滥用是许多程序员关注的一个重大问题,那么文档中就会出现警告或代码中会出现修复。你的公司太多了,不可能独立发现一个微不足道的优化问题。

  • 我见过使用重载调试日志的C程序,其中printf会带来大量开销。但那是因为C的其余部分太瘦了。如果C具有垃圾收集、动态名称解析和列表理解等功能,格式化的I/O将开始显得廉价。

使用日志模块最有效的方法是最容易编程的方法,因为你的时间(我希望!)并不便宜。如果有人抱怨你的"浪费周期",请他展示它是如何占你程序运行时间的1%的。然后你可以把注意力放在重要的事情上。

最新更新