Sid*_*Sid 11 python performance logging multithreading python-multithreading
我正在研究Python中的高性能日志记录,到目前为止,我们已经对python标准日志记录模块的性能感到失望 - 但似乎没有其他选择.下面是一段性能测试代码4种不同的日志记录方式:
import logging
import timeit
import time
import datetime
from logutils.queue import QueueListener, QueueHandler
import Queue
import threading
tmpq = Queue.Queue()
def std_manual_threading():
start = datetime.datetime.now()
logger = logging.getLogger()
hdlr = logging.FileHandler('std_manual.out', 'w')
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
def logger_thread(f):
while True:
item = tmpq.get(0.1)
if item == None:
break
logging.info(item)
f = open('manual.out', 'w')
lt = threading.Thread(target=logger_thread, args=(f,))
lt.start()
for i in range(100000):
tmpq.put("msg:%d" % i)
tmpq.put(None)
lt.join()
print datetime.datetime.now() - start
def nonstd_manual_threading():
start = datetime.datetime.now()
def logger_thread(f):
while True:
item = tmpq.get(0.1)
if item == None:
break
f.write(item+"\n")
f = open('manual.out', 'w')
lt = threading.Thread(target=logger_thread, args=(f,))
lt.start()
for i in range(100000):
tmpq.put("msg:%d" % i)
tmpq.put(None)
lt.join()
print datetime.datetime.now() - start
def std_logging_queue_handler():
start = datetime.datetime.now()
q = Queue.Queue(-1)
logger = logging.getLogger()
hdlr = logging.FileHandler('qtest.out', 'w')
ql = QueueListener(q, hdlr)
# Create log and set handler to queue handle
root = logging.getLogger()
root.setLevel(logging.DEBUG) # Log level = DEBUG
qh = QueueHandler(q)
root.addHandler(qh)
ql.start()
for i in range(100000):
logging.info("msg:%d" % i)
ql.stop()
print datetime.datetime.now() - start
def std_logging_single_thread():
start = datetime.datetime.now()
logger = logging.getLogger()
hdlr = logging.FileHandler('test.out', 'w')
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
for i in range(100000):
logging.info("msg:%d" % i)
print datetime.datetime.now() - start
if __name__ == "__main__":
"""
Conclusion: std logging about 3 times slower so for 100K lines simple file write is ~1 sec while std
logging ~3. If threads are introduced some overhead causes to go to ~4 and if QueueListener and events
are used with enhancement for thread sleeping that goes to ~5 (probably because log records are being
inserted into queue).
"""
print "Testing"
#std_logging_single_thread() # 3.4
std_logging_queue_handler() # 7, 6, 7 (5 seconds with sleep optimization)
#nonstd_manual_threading() # 1.08
#std_manual_threading() # 4.3
Run Code Online (Sandbox Code Playgroud)
我的问题是,为什么日志模块会有如此多的性能开销,还有其他选择吗?作为性能敏感的应用程序,使用日志记录模块甚至有意义吗?
ps:我已经分析了不同的场景,看起来LogRecord创建很昂贵.
stdlib logging
软件包为开发人员/ devops /支持人员提供了很多灵活性和功能,并且显然需要付出一定的代价。如果对性能的要求胜于对灵活性的要求,那么您需要考虑其他事项。您是否按照文档中所述的步骤进行了优化?在合理的硬件上,典型的日志记录调用需要数十微秒的时间,这似乎很少。但是,仅由于生成的信息量可能需要花费大量时间才能解决,很少建议使用紧密循环。
查找调用者的代码可能非常昂贵,但是如果您想要进行记录调用的文件名和行号,则需要此代码。
QueueHandler
适用于日志记录I / O将花费大量时间且无法在带内完成的情况。例如,需要通过电子邮件将日志发送给站点管理员的Web应用程序不能冒险SMTPHandler
直接使用,因为电子邮件握手可能很慢。
不要忘记Python中的线程上下文切换很慢。你尝试了SocketHandler
吗?在文档中有一个合适的起点,用于一个单独的接收器进程,该进程执行对文件,电子邮件等的实际I / O。因此,您的进程仅在执行套接字I / O,而不仅在进行日志记录上下文切换。使用域套接字或UDP可能仍然更快,尽管后者当然是有损的。
还有其他优化方法。例如,为了emit()
确保线程安全,日志记录中的标准处理程序会进行锁定,如果在您控制下的特定情况下,该处理程序没有争用,则可以有一个不操作获取和释放锁的处理程序子类。等等。
如果您想要更好的答案,请尝试更详细地描述您的问题,为什么需要记录如此大量的消息?日志记录旨在记录重要信息,尤其是警告和错误,而不是您执行的每一行。
如果日志记录占用了超过 1% 的处理时间,则可能是您使用错误,这不是日志记录错误。
其次,与性能相关:在将消息发送到日志记录模块之前不要构建消息(将 format % params 替换为 format command params)。这是因为日志记录为您执行此操作,但速度要快得多。