python日志记录性能比较和选项

python日志记录性能比较和选项,python,multithreading,performance,logging,python-multithreading,Python,Multithreading,Performance,Logging,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

我正在研究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
  • NonsD_manual_threading选项工作得最好,因为没有日志模块的开销,但显然您错过了许多功能,例如格式化程序、过滤器和漂亮的界面
  • 单线程中的std_日志记录是次好的选择,但仍然比非std手动线程慢3倍左右
  • std_manual_threading选项将消息转储到线程安全队列中,并在单独的线程中使用标准日志模块。这比选项2高出约25%,可能是由于上下文转换成本
  • 最后,使用“logutils”的QueueHandler的选项是最昂贵的。我调整了logutils/queue.py的_monitor方法的代码,使其在处理500条消息后休眠10毫秒,只要队列中的消息少于100K条。这将运行时从7秒减少到5秒(可能是因为避免了上下文切换成本)
  • 我的问题是,为什么日志模块会有这么多的性能开销,还有其他选择吗?作为一个性能敏感的应用程序,使用日志模块是否有意义


    p、 美国:我已经分析了不同的场景,似乎创建日志记录的成本很高。

    如果你想要更好的答案,请尝试更详细地描述你的问题,为什么你需要这么大的成本 要记录的邮件数?日志记录旨在记录重要信息,特别是警告和错误,而不是记录您执行的每一行

    如果日志记录占用了超过1%的处理时间,则可能是您错误地使用了它,而这不是日志记录错误


    第二,与性能相关:在将消息发送到日志模块之前,不要构建消息(用format命令params替换format%params)。这是因为日志记录为您完成了这项工作,但速度要快得多。

    stdlib
    日志记录
    软件包为开发人员/devops/支持人员提供了很多灵活性和功能,而且这种灵活性显然要付出一些代价。如果对性能的需求超过了对灵活性的需求,那么您需要做其他事情。您是否采取了优化所描述的步骤?在合理的硬件上,一个典型的日志调用需要几十微秒的时间,这似乎并不过分。然而,如果仅仅因为生成的信息量可能需要花费太多的时间,那么在紧密循环中登录是不可取的

    查找调用者的代码可能非常昂贵,但如果您需要,例如文件名和进行日志记录调用的行号,则需要使用该代码

    QueueHandler
    适用于记录I/O将花费大量时间且无法在带内完成的情况。例如,其日志需要通过电子邮件发送给站点管理员的web应用程序不能冒险直接使用
    SMTPHandler
    ,因为电子邮件握手可能很慢

    不要忘记Python中的线程上下文切换很慢。你试过SocketHandler吗?单独的接收者进程有一个合适的起点,它对文件、电子邮件等进行实际的I/O。因此,您的进程只进行套接字I/O,而不进行上下文切换,仅用于日志记录。使用域套接字或UDP可能更快,尽管后者当然是有损的


    还有其他的优化方法。例如,日志记录中的标准处理程序围绕
    emit()
    执行锁定,以确保线程安全-如果在您控制的特定场景中没有对处理程序的争用,则可以使用不操作锁获取和释放的处理程序子类。等等。

    传统意义上的Python并不是真正的多线程。每当线程执行时,它都必须拥有gil(全局解释器锁)。每当“线程”调用系统或必须等待IO时,它们都会让步。这允许解释器线程运行其他python“线程”。这相当于异步I/O

    无论是使用还是删除日志消息的结果,评估日志消息参数的所有工作都已完成。如其他答复所述。 然而,遗漏的是(您问题中的多线程部分涉及到的是)虽然将大量数据写入磁盘可能会很慢,因为现代计算机有许多内核,但将输出写入文件的过程将分配给另一个内核,而解释器将转移到另一个python“线程”。操作系统将完成异步磁盘写入,磁盘写入几乎不会浪费时间


    只要解释器总是有另一个线程要切换,写操作就不会浪费时间。如果所有python“线程”都在I/O上被阻塞,解释器实际上只会损失时间。除非你真的把磁盘弄得满满的,否则这种情况很可能会发生。

    冒着成为“那个家伙”的风险--您希望从您的流程中生成多少行输出,您需要写入多少行输出以额外花费1秒的计算时间?日志记录占总计算时间的百分比是多少?似乎在您注意到运行时的真正差异之前,您必须记录大量数据——至少对于运行时间较长的流程(在重要的地方)……公平点上,但我正试图对日志模块的最佳性能做出明智的决定。我心目中的应用程序每秒接收1000个实时事件,我的记录器执行得越好,我就越觉得“可以”记录这些事件,因为它们有助于重新创建问题发生的条件。Re。你对我答案的评论-谢谢你的数据点。佤邦