C++17 高速非同期ロギングフレームワーク「limlog」の設計と実装

limlog は、マルチスレッド環境でも 1 マイクロ秒台のレイテンシを維持しながら、確実にログをファイルへ書き出す軽量ロギングライブラリです。本稿では内部構造、性能チューニング、および使用方法を詳述します。

設計目標

  • 確実性:すべてのログが欠損なく出力され、スレッド間でログが混在しない。
  • 可読性:1 行 1 ログ、awk 等での解析を容易にする。
  • 利便性:printf よりも cout に近い API、自動ローテーション、ソースコード 2000 行以内。
  • 性能:CPU 数以内でスレッドを増やしても 30 % 以内の劣化、1 ログ 1 µs を目標。

ログフォーマット

ファイル名 : app.YYYYMMDD.HHMMSS.連番.log
一行フォーマット : YYYYMMDD HH:MM:SS.usec TID LEVEL メッセージ - ファイル:関数名:行

公開インターフェース

マクロを用いた簡潔な API を提供します。


#include <limlog/limlog.hpp>

int main() {
    limlog::set_file("app");
    limlog::set_level(limlog::Level::DEBUG);
    limlog::set_roll_size(64); // MB

    LOG_INFO  << "start";
    LOG_WARN  << "value=" << 42;
    LOG_ERROR << "oops" << limlog::end;
}

アーキテクチャ

フロントエンド(ログ生成)とバックエンド(ログ出力)を完全に分離し、thread_local バッファとシングルコンシューマスレッドを採用してロックフリーに近い動作を実現します。

バックエンド

唯一の LimLog シングルトンが、起動時に専用スレッドを生成し、各スレッドの thread_local BlockingBuffer を巡回してファイルへ書き込みます。


class LimLog {
    ...
    std::vector<BlockingBuffer*> buffers_;
    std::thread                sink_;
    std::mutex                 mtx_;
    std::condition_variable    cv_;
    char*                      out_buf_  = nullptr; // 16 MB
    static thread_local BlockingBuffer* local_buf_;
};

シャットダウン時は二段階の条件変数で残留データを確実にフラッシュします。

フロントエンド

LogLine 一時オブジェクトが operator<< で内部バッファに直接書き込みます。


struct LogLine {
    LogLine(Level lv, const char* file, const char* func, int line);
    template<typename T>
    LogLine& operator<<(const T& v) {
        // 高速フォーマット後、バックエンドへ転送
        return *this;
    }
};

性能最適化

時刻取得

  • Linux では clock_gettime(CLOCK_REALTIME_COARSE) を使用し、秒単位でキャッシュ。
  • マイクロ秒以下は % 1'000'000 で算出。

スレッド ID


static thread_local pid_t cached_tid = 0;
pid_t get_tid() {
    if (cached_tid == 0) cached_tid = syscall(SYS_gettid);
    return cached_tid;
}

整数→文字列変換

桁ごとの事前計算テーブルを用いた逐次除算方式により、std::to_string より 3~4 倍高速化。

ベンチマーク

Intel Core i7-9700K、WSL1、ext4 環境で測定。

スレッド数平均レイテンシ (µs/1 ログ)スループット (MB/s)
10.55180
40.61175
80.72170

1 ログ 80 byte 想定。8 スレッドでも 30 % 以内の劣化に収まっています。

テストスイート

  • test_timestamp():日付ロールオーバー検証
  • test_blocking_buffer():リングバッファ境界条件
  • test_itoa():整数変換精度チェック

今後の課題

  • 浮動小数点数の高速フォーマット(Ryu アルゴリズム導入)
  • 時刻取得のコスト削減(TSC ベアリング)
  • 非同期ローテーション(ディスク I/O ブロック回避)

タグ: C++17 asynchronous-logging thread-local-storage ring-buffer high-performance-io

5月25日 09:36 投稿