引言
本章完善了Zig中日常操作工具:精确时间测量(std.time)、结构化日志(std.log)和终端友好的进度报告(std.Progress)。在这里,我们使管道可观察、可测量且用户友好。time.ziglog.zigProgress.zig
我们将专注于在Zig 0.15.2下跨平台工作的确定性片段,突出陷阱、性能注意事项和最佳实践。
使用 std.time 进行时间管理
Zig的 std.time 提供:
- 日历时间戳:timestamp(), milliTimestamp(), microTimestamp(), nanoTimestamp()。
- 持续时间/单位:诸如 ns_per_ms, ns_per_s, s_per_min 等常量用于转换。
- 高精度计时器:Instant(快速,不严格单调)和 Timer(通过饱和实现单调行为)。
一般来说,在测量经过的持续时间时更喜欢使用 Timer。只有当您需要更快的采样并能容忍来自 quirky OS/固件环境的偶尔非单调性时,才使用 Instant。
测量经过的时间 (Timer)
Timer 产生单调读数(在回归时饱和),是基准测试和超时的理想选择。39
const std = @import("std");
pub fn main() !void {
var t = try std.time.Timer.start();
std.Thread.sleep(50 * std.time.ns_per_ms);
const ns = t.read();
// Ensure we slept at least 50ms
if (ns < 50 * std.time.ns_per_ms) return error.TimerResolutionTooLow;
// Print a stable message
std.debug.print("Timer OK\n", .{});
}
$ zig run time_timer_sleep.zigTimer OK
睡眠使用 std.Thread.sleep(ns)。在大多数操作系统上,粒度约为1ms或更差;计时器与底层时钟一样精确。Thread.zig
瞬时采样和排序
Instant.now() 为当前进程提供快速、高精度的时间戳。它尝试在暂停期间前进,可以比较或求差。它不能保证在所有地方都严格单调。当您需要强制执行该属性时,请使用 Timer。
const std = @import("std");
pub fn main() !void {
const a = try std.time.Instant.now();
std.Thread.sleep(1 * std.time.ns_per_ms);
const b = try std.time.Instant.now();
if (b.order(a) == .lt) return error.InstantNotMonotonic;
std.debug.print("Instant OK\n", .{});
}
$ zig run time_instant_order.zigInstant OK
时间单位转换
更喜欢使用提供的单位常量而不是手工编写的数学运算。它们提高清晰度并防止混合单位中的错误。
const std = @import("std");
pub fn main() !void {
const two_min_s = 2 * std.time.s_per_min;
const hour_ns = std.time.ns_per_hour;
std.debug.print("2 min = {d} s\n1 h = {d} ns\n", .{ two_min_s, hour_ns });
}
$ zig run time_units.zig2 min = 120 s 1 h = 3600000000000 ns
使用 std.log 进行日志记录
std.log 是一个小型、可组合的日志外观。您可以:
- 通过 std_options 控制日志级别(默认值取决于构建模式)。
- 使用作用域(命名空间)来分类消息。
- 提供自定义 logFn 来更改格式或重定向。
在下面,我们设置 .log_level = .info 以抑制调试日志,并演示默认和作用域日志记录。
const std = @import("std");
// Configure logging for this program
pub const std_options: std.Options = .{
.log_level = .info, // hide debug
.logFn = std.log.defaultLog,
};
pub fn main() void {
std.log.debug("debug hidden", .{});
std.log.info("starting", .{});
std.log.warn("high temperature", .{});
const app = std.log.scoped(.app);
app.info("running", .{});
}
$ zig run logging_basic.zig 2>&1 | catinfo: starting warning: high temperature info(app): running
注意:
- 默认日志记录器写入stderr,因此我们在上面使用 2>&1 以在本书中内联显示。
- 在Debug构建中,默认级别是 .debug。通过 std_options 覆盖以使示例在优化模式间保持稳定。
使用 std.Progress 进行进度报告
std.Progress 在终端上绘制一个小任务树,定期从另一个线程更新。它不分配内存,旨在跨终端和Windows控制台可移植。使用它来表示长时间运行的工作,如构建、下载或分析过程。
以下演示在练习API(根节点、子节点、completeOne、end)时禁用打印以获得确定性输出。在实际工具中,省略 disable_printing 以呈现动态进度视图。
const std = @import("std");
pub fn main() void {
// Progress can draw to stderr; disable printing in this demo for deterministic output.
const root = std.Progress.start(.{ .root_name = "build", .estimated_total_items = 3, .disable_printing = true });
var compile = root.start("compile", 2);
compile.completeOne();
compile.completeOne();
compile.end();
var link = root.start("link", 1);
link.completeOne();
link.end();
root.end();
}
$ zig run progress_basic.zigno output
提示:
- 使用 Options.estimated_total_items 显示计数("[3/10] compile");
- 使用 setName 更新名称;
- 通过 std.Progress.setStatus 发出成功/失败信号。
注意事项和警告
- Timer 与 Instant:对于经过时间和单调行为更喜欢
Timer;当偶尔的非单调性可接受,对于快速采样使用Instant。 - 睡眠分辨率取决于操作系统。不要假设亚毫秒精度。
- 日志过滤器按作用域应用。使用
scoped(.your_component)干净地屏蔽嘈杂的子系统。 std.Progress输出适应终端功能。在CI/非TTY或禁用打印时,不写入任何内容。- 时区支持:stdlib在0.15.2中尚没有稳定的
std.tz模块。如果您需要时区数学,请使用平台API或库。[待定]
练习
- 编写一个使用
Timer的微基准测试来比较两种格式化例程。打印更快的一种以及快了多少微秒。 - 用自定义
logFn包装std.log,该函数用nanoTimestamp()前缀时间戳。确保它保持无分配。 - 使用
std.Progress创建一个显示三个阶段的小型构建模拟器。使第二阶段动态增加estimated_total_items。
Open Questions
- std中的时区辅助函数:未来
std.tz或等效物的状态和路线图?[待定]