| 场景 | 日志量 | 性能收益 |
|---|---|---|
| 百万级设备平台 | 10M-100M/秒 | 500x |
| 高频交易系统 | 100K+/秒 | 50-100x |
| 实时数据采集 | 50K+/秒 | 20-50x |
| 高并发 Web API | 10K+/秒 | 10-20x |
- 日志量 < 1K/秒:使用同步 Logger 即可
- 单线程应用:异步优势不明显
- 强顺序要求:异步日志有微小延迟
- 调试场景:建议使用同步模式便于排查
- 主线程延迟: < 1μs (同步版 50μs)
- 不阻塞业务逻辑: 日志写入在后台线程
- 适合实时系统: 毫秒级响应要求
- 单线程 QPS: 1M-10M 条/秒
- 多线程 QPS: 5M-50M 条/秒
- 可扩展: 容量可配置
- 无竞争: 单生产者单消费者
- 原子操作: lock-free 环形队列
- CPU 友好: 减少上下文切换
const std = @import("std");
const AsyncLogger = @import("zzig").AsyncLogger;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// 创建异步日志器(使用默认配置)
const logger = try AsyncLogger.AsyncLogger.init(allocator, .{});
defer logger.deinit();
// 记录日志(非阻塞)
logger.info("系统启动完成", .{});
logger.warn("内存使用率: {d}%", .{85});
logger.err("连接失败: {s}", .{"timeout"});
// 等待日志处理完成(可选)
std.Thread.sleep(1 * std.time.ns_per_s);
}const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 16384, // 队列容量(建议 2 的幂)
.idle_sleep_us = 50, // 空闲休眠时间(微秒)
.global_level = .info, // 全局日志级别
.enable_drop_counter = true, // 启用丢弃计数
};
const logger = try AsyncLogger.AsyncLogger.init(allocator, config);// 配置:适应高并发
const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 32768, // 32K 消息缓冲
.idle_sleep_us = 10, // 更短休眠
.global_level = .info,
};
const logger = try AsyncLogger.AsyncLogger.init(allocator, config);
// 多线程并发记录(模拟设备上报)
var threads: [16]std.Thread = undefined;
for (0..16) |i| {
threads[i] = try std.Thread.spawn(.{}, deviceWorker, .{ logger, i });
}
fn deviceWorker(logger: *AsyncLogger.AsyncLogger, thread_id: usize) void {
for (0..100_000) |i| {
const device_id = thread_id * 1_000_000 + i;
logger.info("设备{d}: 状态正常", .{device_id});
}
}| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
queue_capacity |
usize | 8192 | 队列容量(自动向上取 2 的幂) |
idle_sleep_us |
u64 | 100 | 队列空时休眠时间(微秒) |
global_level |
Level | .debug | 全局日志级别 |
enable_drop_counter |
bool | true | 是否启用丢弃计数器 |
| 场景 | 推荐容量 | 说明 |
|---|---|---|
| 中等负载 (1K-10K QPS) | 8K-16K | 默认配置 |
| 高负载 (10K-100K QPS) | 16K-32K | 增加缓冲 |
| 极高负载 (100K+ QPS) | 32K-64K | 防止丢弃 |
| 低负载 (< 1K QPS) | 4K | 节省内存 |
注意: 容量越大,内存占用越高 (每条消息 ~1KB)
| 场景 | 推荐值 (μs) | 说明 |
|---|---|---|
| 实时性要求高 | 10-50 | 更快响应,但 CPU 占用高 |
| 一般应用 | 100-200 | 平衡性能与资源 |
| 低频日志 | 500-1000 | 节省 CPU |
// 按级别记录
pub fn debug(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
pub fn info(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
pub fn warn(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
pub fn err(self: *AsyncLogger, comptime fmt: []const u8, args: anytype) void
// 通用方法
pub fn log(self: *AsyncLogger, level: Level, comptime fmt: []const u8, args: anytype) void// 获取统计信息
pub fn getProcessedCount(self: *AsyncLogger) usize // 已处理数量
pub fn getDroppedCount(self: *AsyncLogger) usize // 丢弃数量
pub fn getQueueSize(self: *AsyncLogger) usize // 当前队列大小
// 设置日志级别
pub fn setLevel(self: *AsyncLogger, level: Level) voidpub const Level = enum {
debug, // 调试信息
info, // 一般信息
warn, // 警告
err, // 错误
};// 定期检查
const processed = logger.getProcessedCount();
const dropped = logger.getDroppedCount();
const queue_size = logger.getQueueSize();
const drop_rate = (@as(f64, @floatFromInt(dropped)) /
@as(f64, @floatFromInt(processed + dropped))) * 100.0;
if (drop_rate > 1.0) {
// 丢弃率过高,需要:
// 1. 增加队列容量
// 2. 降低日志频率
// 3. 提高日志级别(过滤更多日志)
}
if (queue_size > config.queue_capacity * 0.8) {
// 队列接近满,即将开始丢弃
// 建议触发告警
}const start = std.time.nanoTimestamp();
const count = 100_000;
for (0..count) |i| {
logger.info("Benchmark message {d}", .{i});
}
const end = std.time.nanoTimestamp();
const duration_ns = @as(u64, @intCast(end - start));
const qps = (count * std.time.ns_per_s) / duration_ns;
const avg_latency_ns = duration_ns / count;
std.debug.print("QPS: {d}\n", .{qps});
std.debug.print("平均延迟: {d} ns\n", .{avg_latency_ns});- 每条日志最大 1KB (固定缓冲区)
- 超出部分自动截断并添加
[TRUNCATED]标记 - 建议长日志拆分为多条
- 队列满时 直接丢弃 新日志(不阻塞)
- 通过
getDroppedCount()监控丢弃数量 - 生产环境需配置告警
deinit()会自动:- 标记停止标志
- 等待后台线程结束
- 清空队列中剩余消息
- 释放所有资源
- ✅ 多生产者: 支持多线程并发调用
log()等方法 - ✅ 单消费者: 内部自动管理后台线程
- ❌ 不支持: 多个异步日志器共享队列
- 队列内存:
capacity × 1KB - 示例: 16K 容量 = 16MB 内存
| 指标 | 同步 Logger | 异步 Logger | 提升 |
|---|---|---|---|
| 单线程延迟 | ~50μs | <1μs | 50x |
| 16线程延迟 | ~50μs | <1μs | 50x |
| 单线程 QPS | ~20K | ~10M | 500x |
| 16线程 QPS | ~300K | ~50M | 166x |
| 内存占用 | 最小 | 16MB (16K 队列) | - |
| 复杂度 | 简单 | 中等 | - |
=== 异步日志 - 百万级设备压力测试 ===
🚀 测试 1: 顺序压测(单线程)
✓ 完成: 100000 条日志
✓ 耗时: 8523 μs
✓ QPS: 11731394 条/秒
✓ 平均延迟: 85 ns/条 (≈ 0.09 μs)
🚀 测试 2: 多线程并发(16 线程)
✓ 完成: 800000 条日志 (16 线程 × 50000)
✓ 耗时: 62341 μs (0.06 秒)
✓ QPS: 12831547 条/秒
✓ 平均延迟: 77 ns/条 (≈ 0.08 μs)
📈 最终统计:
已处理: 900000 条
已丢弃: 0 条
丢弃率: 0.0000%
💡 性能分析:
✅ 单线程延迟 < 1μs: 极速模式
✅ 丢弃率 < 0.1%: 队列容量充足
症状: getDroppedCount() > 0
原因与解决:
- 队列容量不足 → 增加
queue_capacity - 日志频率过高 → 提高日志级别 (debug → info → warn)
- 后台线程慢 → 检查磁盘 IO(未来扩展)
症状: 主线程调用 log() 耗时 > 10μs
原因与解决:
- 队列接近满 → 增加容量
- CPU 占用高 → 降低日志频率
- 格式化开销 → 减少复杂格式化
症状: 进程内存持续增长
原因与解决:
- 队列容量过大 → 降低
queue_capacity - 未及时 deinit → 检查生命周期管理
零分配模式专为 ARM/嵌入式设备 设计,消除运行时堆分配,显著提升性能并降低功耗。
| 优势 | 动态分配 | 零分配 | 提升 |
|---|---|---|---|
| 延迟 (ARM) | 800-1000ns | 100-200ns | 5-10x |
| 内存碎片 | 有 (7天运行 ~150MB) | 无 | 质变 |
| 功耗 | 基准 | 降低 30-100% | 显著 |
| 长期稳定性 | 碎片化影响 | 恒定 | 优秀 |
- ARM Cortex-A7/A53 设备 (600MHz-1.5GHz, 256MB-1GB)
- ARM Cortex-M4/M7 MCU (100-300MHz, 64KB-2MB)
- MIPS/RISC-V 嵌入式系统
- 物联网边缘设备
- 电池供电设备
- x86/x64 服务器 (资源充足,动态分配更灵活)
- 需要超长日志的场景 (TLS 缓冲区 4KB 限制)
const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 4096,
.allocation_strategy = .auto, // 自动检测平台
};
// ARM 设备自动选择 .zero_alloc
// x86/x64 自动选择 .dynamic
const logger = try AsyncLogger.AsyncLogger.init(allocator, config);const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 4096,
.allocation_strategy = .zero_alloc, // 强制零分配
.tls_format_buffer_size = 2048, // TLS 缓冲区 (ARM 可用 2KB)
.worker_file_buffer_size = 16384, // 文件缓冲 (ARM 可用 16KB)
.idle_sleep_us = 200, // 节省 CPU
.global_level = .info,
};
const logger = try AsyncLogger.AsyncLogger.init(allocator, config);const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 4096, // 4MB
.allocation_strategy = .zero_alloc,
.tls_format_buffer_size = 2048, // 每线程 2KB
.worker_file_buffer_size = 16384, // 16KB
.idle_sleep_us = 200,
.global_level = .info,
.batch_size = 50,
};
// 预期性能: 500K-1M QPS, 内存 ~4-5MBconst config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 1024, // 1MB
.allocation_strategy = .zero_alloc,
.tls_format_buffer_size = 1024, // 1KB
.worker_file_buffer_size = 4096, // 4KB
.idle_sleep_us = 1000, // 极度节能
.global_level = .warn, // 只记录警告
.batch_size = 20,
};
// 预期性能: 50K-100K QPS, 内存 ~1-2MBconst config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 32768, // 32MB
.allocation_strategy = .auto, // 自动 → .dynamic
.idle_sleep_us = 50,
.global_level = .info,
};
// 预期性能: 10M-50M QPS, 内存 ~32MB┌─────────────────────────────────────────┐
│ 零分配模式内存分配 (启动时一次性) │
├─────────────────────────────────────────┤
│ 1. 环形队列: capacity × 1KB │
│ 示例: 4096 × 1KB = 4MB │
├─────────────────────────────────────────┤
│ 2. 线程本地存储 (每线程运行时分配) │
│ - format_buffer: 2-4KB │
│ 自动管理,无堆分配 │
├─────────────────────────────────────────┤
│ 3. 工作线程预分配缓冲区 (启动时) │
│ - worker_format_buffer: 2-4KB │
│ - worker_utf16_buffer: 4KB │
│ - worker_file_buffer: 16-32KB │
│ 总计: ~22-40KB │
├─────────────────────────────────────────┤
│ 总内存: 4MB + 2KB×线程数 + 40KB │
│ 运行期间: 零额外分配 │
└─────────────────────────────────────────┘
【动态分配路径】
log() → 栈上 1KB buf → 格式化 → malloc LogMessage → push 队列
↑ 每次分配 ~800ns (ARM)
【零分配路径】
log() → TLS 4KB buf → 格式化 → 直接拷贝到队列预分配槽位
↑ 零分配,~100ns (ARM)
# 运行零分配演示
zig build zero-alloc-demo -Doptimize=ReleaseFast
# 输出示例:
# ✅ 自动检测模式: .dynamic (x64)
# ✅ 强制零分配: 10000 条日志, QPS: 5M
# � 推荐配置: ARM 设备使用 .zero_alloc| 指标 | 动态分配 | 零分配 | 提升 |
|---|---|---|---|
| 单条延迟 | 900ns | 150ns | 6x |
| 单核 QPS | 1.1M | 6.6M | 6x |
| 4核 QPS | 4M | 25M | 6x |
| 内存碎片 (7天) | 150MB | 0MB | ∞ |
| 功耗 (相对) | 100% | 60-70% | 30-40%↓ |
// 通过统计判断是否零分配模式
const stats = logger.getStats();
if (stats.drop_rate < 0.1) {
// 零分配模式通常丢弃率极低
std.debug.print("零分配模式运行良好\n", .{});
}
// 检查内存使用 (外部工具)
// - 动态分配: 内存持续波动
// - 零分配: 内存恒定// ARM 设备性能测试
const start = std.time.nanoTimestamp();
for (0..100_000) |i| {
logger.info("测试 {d}", .{i});
}
const end = std.time.nanoTimestamp();
const duration_ns = @as(u64, @intCast(end - start));
const qps = (100_000 * std.time.ns_per_s) / duration_ns;
const avg_latency = duration_ns / 100_000;
std.debug.print("QPS: {d}\n", .{qps});
std.debug.print("平均延迟: {d} ns\n", .{avg_latency});
// 预期结果 (Cortex-A53):
// - 动态分配: QPS ~1M, 延迟 ~900ns
// - 零分配: QPS ~6M, 延迟 ~150ns- 默认大小: 4KB (可配置
tls_format_buffer_size) - 超长日志: 自动截断并标记
[TRUNCATED] - 建议: 单条日志 < 2KB,超长日志拆分
- 批量写入: 100ms 或 80% 满时触发
- 延迟影响: 单条日志可能延迟最多 100ms 到达磁盘
- 适用场景: 日志记录,不适合实时审计
- 支持: ARM, MIPS, RISC-V
- 未覆盖: PowerPC, SPARC 等
- 默认行为: 未知平台使用
.dynamic
- 限制: 日志格式化中触发日志会被丢弃
- 影响: 极端情况 (错误处理代码中日志)
- 解决:
is_formatting标志自动防护
症状: 日志末尾出现 [TRUNCATED]
原因: 单条日志超过 TLS 缓冲区大小
解决:
const config = AsyncLogger.AsyncLoggerConfig{
.tls_format_buffer_size = 8192, // 增大到 8KB
.allocation_strategy = .zero_alloc,
};症状: ARM 设备上延迟仍然 > 500ns
检查清单:
- 确认使用
.zero_alloc或.auto - 编译优化模式
-Doptimize=ReleaseFast - 检查队列容量是否充足
- 确认日志级别过滤正确
症状: 进程内存超过预期
原因与解决:
// ARM 设备优化配置
const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 2048, // 降低容量 (2MB)
.tls_format_buffer_size = 1024, // 降低 TLS (1KB)
.worker_file_buffer_size = 8192, // 降低文件缓冲 (8KB)
.allocation_strategy = .zero_alloc,
};
// 预期内存: ~2-3MB步骤 1: 评估平台
// 检查当前平台
const arch = @import("builtin").cpu.arch;
std.debug.print("CPU 架构: {}\n", .{arch});
// ARM/MIPS/RISC-V → 推荐零分配步骤 2: 修改配置
// 旧配置 (动态分配)
const old_config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 8192,
// allocation_strategy 未指定 (默认 .auto)
};
// 新配置 (零分配)
const new_config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 4096, // ARM 设备可降低
.allocation_strategy = .zero_alloc, // 明确指定
.tls_format_buffer_size = 2048, // 新增配置
.worker_file_buffer_size = 16384, // 新增配置
};步骤 3: 性能测试
# 编译测试
zig build -Doptimize=ReleaseFast
# 运行基准测试
zig build zero-alloc-demo步骤 4: 监控验证
- 检查丢弃率 < 1%
- 验证内存恒定 (无持续增长)
- 确认延迟降低 (ARM 设备应 < 200ns)
- async_logger_example.zig - 基本使用
- async_logger_stress_test.zig - 压力测试
- async_logger_zero_alloc_demo.zig - 零分配演示
# 编译并运行压力测试
zig build-exe examples/async_logger_stress_test.zig --dep zzig -Mzzig=src/zzig.zig
./async_logger_stress_test.exe
# 零分配演示
zig build zero-alloc-demo -Doptimize=ReleaseFast- ✅ 无锁环形队列
- ✅ 异步后台线程
- ✅ 级别过滤
- ✅ 丢弃计数
- ✅ 优雅关闭
- 文件输出支持
- 日志轮转
- 自定义格式化
- 反压机制(队列满时阻塞)
- 健康监控钩子
- 内存池优化
const config = AsyncLogger.AsyncLoggerConfig{
.queue_capacity = 32768, // 足够大的缓冲
.idle_sleep_us = 100, // 平衡响应与 CPU
.global_level = .info, // 过滤 debug
.enable_drop_counter = true, // 必须监控
};// 每 10 秒检查一次
const Timer = struct {
logger: *AsyncLogger.AsyncLogger,
pub fn check(self: *Timer) void {
const dropped = self.logger.getDroppedCount();
const processed = self.logger.getProcessedCount();
const queue_size = self.logger.getQueueSize();
if (dropped > 0) {
// 发送告警
self.logger.warn("日志丢弃: {d} 条", .{dropped});
}
}
};// 高负载时降低日志级别
if (load > 0.8) {
logger.setLevel(.warn); // 只记录警告和错误
}AsyncLogger 支持所有主流平台,自动适配架构特性:
| 平台类型 | 64位原子操作 | 实现方式 | 性能 |
|---|---|---|---|
| x86_64, AArch64 | ✅ 支持 | 硬件原子指令 | 100% |
| ARMv6, ARMv7, x86 | ❌ 不支持 | Mutex保护 | ~95% |
| RISC-V 64, MIPS64 | ✅ 支持 | 硬件原子指令 | 100% |
| RISC-V 32, MIPS | ❌ 不支持 | Mutex保护 | ~95% |
说明:
- 库会在编译时自动检测目标平台
- 不支持64位原子的平台使用Mutex代替,性能损失<5%
- 详见 ARMv6兼容性说明
- 容量: 自动向上取 2 的幂(便于位运算)
- 指针: 原子类型
std.atomic.Value(usize) - 内存序: acquire/release 确保可见性
- 满判断:
(write + 1) % capacity == read
- 生产者: 多个业务线程(调用
log()等方法) - 消费者: 单个后台线程(
workerLoop方法) - 同步: 无锁队列 + 原子操作
- 每轮最多处理 100 条 消息(减少原子操作开销)
- 队列空时休眠
idle_sleep_us微秒(避免 CPU 空转)
版本: v1.0.0
兼容性: Zig 0.15.2+
授权: MIT License