V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
weidaizi
V2EX  ›  程序员

对一些知名或以低延时为目标的 C/C++ 日志库做的 Benchmark

  •  1
     
  •   weidaizi ·
    MuggleWei · 193 天前 · 1724 次点击
    这是一个创建于 193 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目地址:https://github.com/MuggleWei/cc_log_benchmark

    概述

    当前已经有许多开源的 C/C++ 日志库, 本项目对一些知名或以低延时为目标的日志库做一个 Benchmark, 力求发现当面对如 MMORPG Server 或是金融交易系统这类低延时的场景下, 哪些日志库是较好的选择.

    省流,将结果放在前面

    提醒: 有一些图表之外需要注意的内容, 感兴趣的读者可在最后看到

    日志库列表

    此库对几个 C/C++ 日志库做 benchmark, 被测的库如下所示(按字母顺序排序)

    • easyloggingpp: C++ logging library. It is extremely powerful, extendable, light-weight, fast performing, thread and type safe and consists of many built-in features. It provides ability to write logs in your own customized format. It also provide support for logging your classes, third-party libraries, STL and third-party containers etc.
    • fmtlog: fmtlog is a performant fmtlib-style logging library with latency in nanoseconds.
    • glog: C++ implementation of the Google logging module
    • haclog: Haclog(Happy Aync C log) is an extremely fast plain C logging library
    • loguru: A lightweight C++ logging library
    • Nanolog: Nanolog is an extremely performant nanosecond scale logging system for C++ that exposes a simple printf-like API.
    • quill: Asynchronous Low Latency C++ Logging Library
    • reckless: Reckless logging. Low-latency, high-throughput, asynchronous logging library for C++.
    • spdlog: Fast C++ logging library

    Benchmark 说明

    使用 google benchmark 进行测试, 测试分为两部分场景

    1. 场景 1: 设定最小的测试时间 (设置 google benchmark 中的 MinTime), 在这时间中尽可能的压力测试写日志, 此场景主要针对异步日志, 可以反映日志库的吞吐量, 以及当缓冲区高度紧张的情况下日志前端的写入效率如何. 在此场景下测试的线程数量为: 1/2/4/8
    2. 场景 2: 设定迭代和重复次数 (设置 google benchmark 中的 Iterations + Repetitions), 反映在非压力测试的情况下, 各个日志库的表现如何. 在此场景下测试的线程数量为: 1/${0.5 * CPU 数量}/${1 * CPU 数量}/${2 * CPU 数量}

    基准测试

    测试日期: 2023-10-14
    被测库版本, 详见: CMakeLists.txt
    单次写入数据:

    struct LogMsg {
    	uint64_t u64;
    	uint32_t u32;
    	int64_t i64;
    	int32_t i32;
    	char s[128];
    };
    

    日志输出格式:

    ${Level}|${datetime}|${filename}.${line_no}|${func_name}|${thread_id} - u64: msg.u64, i64: msg.i64, u32: msg.u32, i32: msg.i32, s: msg.s
    

    测试机器

    类型: 笔记本
    机器: 20R10002CD ThinkPad X1 Carbon 7th
    系统: Arch Linux x86_64
    Kernel: 6.5.6-arch2-1
    CPU: Intel i7-10710U (12) @ 4.700GHz
    GPU: Intel Comet Lake UHD Graphics
    Memory: 15659MiB
    gcc: (GCC) 13.2.1 20230801
    ldd: (GNU libc) 2.38

    额外说明

    由于我本地机器的测试中遇到一些问题, 导致 fmtlog, quill 和 reckless 没有做到测试场景完全的覆盖

    • 当 fmtlog 加入 #define FMTLOG_BLOCK 1 时, 测试进程会被卡死而无法继续, 所以 fmtlog 只测试了缓冲区满选择丢弃日志的模式
    • 当 quill 使用 UNBOUNDED 时, 在 场景 1 中, 会导致内存使用一直增长而失败, 所以在测试 quill_unbounded 的时候, 跳过 场景 1 的测试
    • reckless 在 场景 1 中会卡住, 所以 reckless 跳过 场景 1 的测试
    • fmtlog 和 Nanolog 我并没有找到设置默认缓冲区的接口, 所以都只使用了默认的缓冲区大小

    由于个人水平所限, 若有错误和纰漏, 亦或是考虑欠妥之处, 还请不吝指正!

    结果分析

    通过上述图表不难看出

    • 在场景 1 中, 较快的日志库有(按字母顺序排序): fmtlog, haclog, Nanolog, quill_bounded
    • 在场景 2 中, 较快的日志库为(按字母顺序排序): fmtlog, haclog, Nanolog

    那么上面提到的这四个日志库为什么更快?他们有什么优劣势,以及使用中有什么要注意的坑呢?

    以上 4 个日志库均为异步日志库, 设计上也都是多缓冲区队列写, 消费者负责轮询的模式, 整体思路相似, 代码细节各有各的趣味. 但作为用户来说, 有一点要特别注意

    特别注意!!!
    特别注意!!!
    特别注意!!!

    既然都使用了缓冲区, 那么就一定要考虑缓冲区被写满的情况, 此时有三种不同的应对方式

    1. 阻塞: 生产者线程阻塞, 等待缓冲区有足够空间后再写入
    2. 丢弃: 放弃写入本条日志, 或者直接套圈写入本条日志但是放弃一整个缓冲区的日志
    3. 扩展: 动态增加缓冲区的长度, 继续写入

    haclog 与 Nanolog 选择了方案 1, fmtlog 可选方案 1/2, quill 可选方案 2/3
    而由于 额外说明 中提到的情况, 导致 fmtlog 只可选择方案 2, quill 在 场景 1 中仅讨论 bounded 模式

    fmtlog

    优点

    • 在两种场景下都表现出良好的速度, 其中在 场景 1 中速度与 quill_bounded 旗鼓相当, 并列最快速度
    • 在两种场景下, 速度表现的稳定性足够好, 波动较小
    • 采用了 format 格式化风格

    缺点

    • 场景 1 中出现了大量丢失日志的情况
    • 场景 2 中也出现了日志丢失的情况
    • 设置 #define FMTLOG_BLOCK 1 情况时, 无法完成 场景 1 的测试

    haclog

    优点

    • 在两种场景下大部分情况都表现出良好的速度, 其中 场景 2 中多线程的情况下, 速度仅略逊于 Nanolog
    • 缓冲区满时采用了阻塞模式, 不会丢日志

    缺点

    • 当吞吐量超过了某个阈值, 缓冲区大量被写满的情况下, 效率会出现大幅下跌

    Nanolog

    优点

    • 在两种场景下都表现出良好的速度, 其中在 场景 2 中多线程的情况下, 速度排名第一
    • 很高的吞吐量, 无论在 场景 1 还是 场景 2 中, 都没有出现效率大幅下跌的情况
    • 缓冲区满时采用了阻塞模式, 不会丢日志

    缺点

    • 日志无法直接阅读, 需要使用附带的 decompressor 程序进行解码
    • 仅支持 Linux

    quill

    优点

    • 场景 1 中速度与 fmtlog 旗鼓相当, 并列最快速度
    • 采用了 format 格式化风格

    缺点

    • 在 bounded 模式下会出现日志丢失的情况
    • 在 unbounded 模式下, 有可能导致内存一直增长, 无法完成 场景 1 的测试

    额外的一点考虑

    haclog 使用纯 C 开发, 而 fmtlog, Nanolog 和 quill 采用 C++ 开发; 当就日志库这个场景来说, 理论上 C++ 实现速度上限会更高一点点, 表现为以下两个方面

    • 编译期计算: C++ 能在编译期预计算好日志参数信息, 而纯 C 实现的日志库需要在第一次运行时计算
    • 日志前端序列化:
      • C 需要在运行时遍历 va_list, 生成的汇编代码是通过循环来逐个参数序列化, 无论是通过 switch 判断预计算好的类型,或是调用预先设置好的函数指针, 都可能会带来一点额外的开销
      • C++ 通过变参模板来进行序列化, 汇编代码可实现为平铺展开的形式, 以空间换时间

    综上所述

    • fmtlog 和 quill 采用 format 格式化风格, 书写方便, 且两者速度稳定性较强, fmtlog 和 quill_bounded 在 场景 1 表现出色. 但是如上一小节所分析的, 他们的速度部分是通过日志丢失换取的, 这在使用场景上需要特别注意
    • haclog 在两种场景下也都表现出色, 在 场景 2 中的表现仅略逊与 Nanolog, 但是在 场景 1 当缓冲区被压满的情况下, 效率大幅度的降低. 这正好与 fmtlog 以及 quill 相反, 他以速度降低来换取了不丢日志
    • Nanolog 在两种场景下都表现出极高的效率, 以及超高的吞吐量. 在 场景 2 下的王者, 在 场景 1 中面临压力的情况下, 保证不丢日志也不用拿太多的效率去换. 不过其超高的吞吐量, 是拿日志非实时可读换取的, 这也导致诸如 tail 一类的工具无法使用

    可以看出, 当前这几个异步日志并没有一个能在全方位碾压其他日志库, 而是都在某一方面做了权衡取舍

    8 条回复    2023-10-18 23:14:48 +08:00
    liprais
        1
    liprais  
       193 天前
    你用了一个带睿频的 cpu 测试的么?
    cnbatch
        2
    cnbatch  
       193 天前
    看完这个测试后,OP 的测试应该是测出了日志库的不可能三角?
    高吞吐、不丢日志、可实时读换
    weidaizi
        3
    weidaizi  
    OP
       193 天前
    @liprais 是的,支持睿频,在测试之前,我已经直接 cpupower 设置成了 performance
    weidaizi
        4
    weidaizi  
    OP
       193 天前
    @cnbatch 👍 哈哈哈,这位朋友总结的专业!
    更严谨一点,应该是: 高吞吐 -> 低时延且高吞吐
    kkocdko
        5
    kkocdko  
       192 天前
    一楼的意思是你应该把 boost=0 ,睿频会有很大的性能波动。benchmark 这种事情本身就是求稳定,才能对各个库都公平。
    weidaizi
        6
    weidaizi  
    OP
       192 天前
    @kkocdko 谢谢您的提醒!
    我之前一直认为,设置了 `cpupower frequency-set -g performance`, 那么默认都应该是固定在最高频率上跑,频率应该是稳定的;您要表达的意思是,如果 turbo boost 开着,我这样设置之后频率依然是不稳定的吗?
    weidaizi
        7
    weidaizi  
    OP
       192 天前   ❤️ 1
    @kkocdko @liprais 👍 查了一番,我之前的理解的确不到位,通过 cpupower 设置为 performance 在睿频开启的情况下的确还是有波动,谢谢两位!之后我会重新出一个关闭了 Turbo Boost 的结果
    weidaizi
        8
    weidaizi  
    OP
       191 天前
    @kkocdko @liprais 更新了一下 benchmark ,增加了 `CPU 频率设置与监控`,固定在 3.2GHz 上测试,再次感谢两位的提醒! 😀
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2835 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:52 · PVG 17:52 · LAX 02:52 · JFK 05:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.