双缓冲+单flush线程是日志落盘最优解,需固定大小buffer(如64KB std::array)、原子指针交换、fdatasync刷盘及预分配格式化,避免realloc、忙等和时钟开销。

直接用 std::ofstream 或裸 write() 同步落盘,业务线程必然卡顿;双缓冲 + 单 flush 线程是平衡性能、可靠性和实现复杂度的最优解,不是“能跑就行”,而是必须按内存布局、原子切换、刷盘语义三者对齐来设计。
双缓冲区为什么不能用 std::vector 动态扩容
动态扩容会触发 realloc(),导致指针失效——而你的写入线程和 flush 线程正通过原子指针共享这块内存。一旦 realloc 发生,flush 线程可能仍在读旧地址,轻则读到垃圾数据,重则段错误。
- buffer 必须是固定大小、生命周期贯穿整个 logger 实例的连续内存
- 推荐用
std::array(64KB)或new char[65536]配合std::unique_ptr管理,确保析构安全 - 大小选 64KB 是经验平衡点:太小(如 4KB)会导致每百条日志就切一次,原子操作和系统调用开销占比飙升;太大(如 2MB)则单次
write()可能阻塞几十毫秒,且不利于 L1/L2 缓存局部性
std::atomic 切换 buffer 的正确姿势
别用 std::atomic 标记“是否满”,那只是把锁从临界区挪到了忙等循环里;真正高效的是让两个 buffer 地址本身可原子交换。
- 声明:
std::atomiccurrent_{buf_a}; std::atomic next_{buf_b}; - 写入线程 append 时只做:
char* b = current_.load();,然后检查剩余空间,拷贝内容,更新本地偏移量 - 满时调用
swap_buffers():用current_.exchange(next_.load())原子换出当前 buffer,再用next_.store(current_.load())指向新空闲区(注意顺序,避免竞态) - 关键约束:swap 过程中绝不 memcpy 内容,只交换指针;memcpy 是写入线程的事,flush 线程只读已交换出的 buffer
后台 flush 线程该用 write() 还是 io_uring
除非你已在 kernel 6.0+ 环境下统一使用 io_uring 做所有 I/O,否则别为日志单独引入它——调试成本高、fallback 逻辑难覆盖、错误码分散,实测吞吐并不比优化后的 write() 高。
- flush 线程只需一个死循环:
while (running_) { auto buf = swap_out_full_buffer(); if (buf) write(fd_, buf, len); fdatasync(fd_); } - 务必调用
fdatasync(),而非fsync()或什么都不调:前者只刷数据页,不刷元数据,延迟更低,且能保证日志落盘后崩溃不丢 - 打开文件时加
O_APPEND | O_CLOEXEC,避免多进程写冲突;不用O_DIRECT——它要求内存对齐、长度对齐,对小日志反而拖慢性能 - 如果真要用
io_uring,必须用posix_memalign(4096, size)分配 buffer,且每个io_uring_sqe必须独立构造、提交后立即重置user_data,否则 completion 乱序
日志 entry 拼接阶段最容易被忽略的性能陷阱
格式化耗时往往远超落盘本身。用 std::ostringstream 拼接时间戳 + 级别 + 消息,压测下比预分配 + fmt::format_to 慢 3 倍以上。
- 所有日志先写入
thread_local char[4096],用snprintf或fmt::format_to直接写入,避免流对象构造/析构开销 - 时间戳用
clock_gettime(CLOCK_MONOTONIC, &ts),不用std::chrono::system_clock::now()——后者可能触发时区计算和 locale 查询 - 文件名用
__FILE_NAME__(GCC 12+),不是__FILE__;行号直接传__LINE__整数,避免字符串转换 - 拼好后整块
memcpy到双缓冲区,不要逐字段拷贝——CPU cache line 对齐的批量拷贝,比多次小拷贝快一个数量级
最易被跳过的细节是:buffer 切换前必须检查是否已满,但“满”的判定不能只看长度,还得预留至少 1 字节用于结尾 \0 或分隔符;否则 flush 线程读到半截日志,解析就会错位。这个边界条件在压力测试里才会暴露,上线后难复现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











