web-dev-qa-db-ja.com

pythonロギングパフォーマンスの比較とオプション

私は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
  1. Nonstd_manual_threadingオプションは、ロギングモジュールのオーバーヘッドがないため最適に機能しますが、フォーマッター、フィルター、Niceインターフェースなどの多くの機能を見逃していることは明らかです。
  2. シングルスレッドでのstd_loggingは次善の策ですが、それでも非std手動スレッドよりも約3倍遅くなります。
  3. Std_manual_threadingオプションは、メッセージをスレッドセーフキューにダンプし、別のスレッドで標準のロギングモジュールを使用します。これは、おそらくコンテキスト切り替えのコストが原因で、オプション2よりも約25%高くなることがわかります。
  4. 最後に、「logutils」のQueueHandlerを使用するオプションが最も高価であることがわかります。 logutils/queue.pyの_monitorメソッドのコードを微調整して、キュー内のメッセージが100K未満である限り、500メッセージを処理した後10ミリ秒間スリープするようにしました。これにより、ランタイムが7秒から5秒に短縮されます(おそらくコンテキスト切り替えコストを回避するため)。

私の質問は、ロギングモジュールでパフォーマンスのオーバーヘッドが非常に大きいのはなぜですか?代替手段はありますか?パフォーマンスに敏感なアプリであるため、ロギングモジュールを使用することは理にかなっていますか?

p.s。:さまざまなシナリオのプロファイルを作成しましたが、LogRecordの作成には費用がかかるようです。

15
Sid

Stdlib loggingパッケージは、開発者/ DevOps /サポートスタッフに多くの柔軟性と機能を提供しますが、その柔軟性には明らかにコストがかかります。パフォーマンスの必要性が柔軟性の必要性よりも優先される場合は、別のものを使用する必要があります。説明されている ドキュメント内 を最適化するための手順を実行しましたか?典型的なロギング呼び出しは、妥当なハードウェアで数十マイクロ秒のオーダーを要しますが、これはほとんど過剰とは思えません。ただし、生成された情報の量が通り抜けるのに時間がかかりすぎる可能性があるという理由だけで、タイトなループにログインすることはほとんどお勧めできません。

発信者を見つけるためのコードは非常に高価になる可能性がありますが、必要に応じて必要になります。ロギング呼び出しが行われたファイル名と行番号。

QueueHandlerは、ロギングI/Oにかなりの時間がかかり、帯域内で実行できないシナリオを対象としています。たとえば、ログを電子メールでサイト管理者に送信する必要があるWebアプリケーションは、電子メールのハンドシェイクが遅くなる可能性があるため、SMTPHandlerを直接使用するリスクを冒すことはできません。

Pythonは遅いです。SocketHandlerを試しましたか?適切な開始点があります ドキュメント内 のスレッドコンテキストの切り替えを忘れないでください。ファイルや電子メールなどへの実際のI/Oを実行する個別のレシーバープロセス。したがって、プロセスはソケットI/Oのみを実行し、ロギングのためだけにコンテキストスイッチを実行しません。ドメインソケットまたはUDPを使用すると、さらに高速になる可能性があります。もちろん損失があります。

最適化する方法は他にもあります。たとえば、ロギングの標準ハンドラーは、スレッドセーフのためにemit()の周りでロックを行います-制御下の特定のシナリオでハンドラーの競合がない場合、ロックを操作しないハンドラーサブクラスを持つことができます取得とリリース。等々。

8
Vinay Sajip

より良い答えが必要な場合は、問題をより詳細に説明してみてください。なぜ、ログに記録するのにこれほど膨大な数のメッセージが必要なのですか。ロギングは、実行するすべての行ではなく、重要な情報、特に警告とエラーを記録するように設計されています。

ロギングに処理時間の1%以上かかる場合は、おそらくそれを誤って使用しており、それはロギングの障害ではありません。

次に、パフォーマンスに関連します。ログモジュールに送信する前にメッセージを作成しないでください(format%paramsをformat command paramsに置き換えます)。これは、ロギングがこれを行うためですが、はるかに高速です。

3
sorin

Pythonは、従来の意味で真にマルチスレッドではありません。スレッドが実行されているときは常に、gil(グローバルインタープリターロック)を所有している必要があります。 「スレッド」は、システムを呼び出すとき、またはIOを待機する必要があるときはいつでも生成されます。これにより、インタープリタースレッドは他のpython "threads"を実行できます。これは、非同期I/Oに相当します。

ロギングメッセージの結果が使用されたかドロップされたかに関係なく、ロギングメッセージの引数を評価するためのすべての作業が実行されます。他の応答で述べたように。ただし、見逃しているのは(そして、質問のマルチスレッド部分が出てくるところです)、最近のコンピューターには多くのコアがあるため、ディスクへの大量の書き込みは遅くなる可能性があります。ファイルへの出力の書き込みプロセスは、インタプリタが別のコアに移動している間、別のコアpython "thread"。オペレーティングシステムは非同期ディスク書き込みを完了し、ディスク書き込みにほとんどまたはまったく時間が失われません。

インタプリタが常に切り替えるための別のスレッドを持っている限り、書き込みに時間が失われることはほとんどありません。インタプリタは、すべてのpython "スレッド"がI/Oでブロックされている場合にのみ、実際に時間を失います。これは、実際にディスクをいっぱいにしている場合を除いて、おそらくそうです。

0
user1424589