macos: cache unified logging loggers per scope

The logFn for macOS unified logging created and released an os_log_t
logger on every single log call. Loggers are now cached per log scope for 
the process lifetime via an atomic pointer (a creation race wastes at most 
one create).

Measured on macOS (Apple Silicon) with local timing instrumentation
during app launch, the version-info logging block in global.init:

  before: 1070us-2629us
  after:   858us-1319us
This commit is contained in:
Mitchell Hashimoto
2026-08-09 15:39:28 -07:00
parent a82637b53a
commit 3225e9ebb1

View File

@@ -140,11 +140,12 @@ fn logFn(
.err => .fault,
};
// Initialize a logger. This is slow to do on every operation
// but we shouldn't be logging too much.
const logger = macos.os.Log.create(build_config.bundle_id, @tagName(scope));
defer logger.release();
logger.log(std.heap.c_allocator, mac_level, prefix ++ format, args);
macosLogger(scope).log(
std.heap.c_allocator,
mac_level,
prefix ++ format,
args,
);
}
stderr: {
@@ -166,6 +167,37 @@ fn logFn(
}
}
/// Returns the macOS unified logging logger for the given scope. The
/// logger is created once per scope and cached for the lifetime of the
/// process, because os_log object creation is slow (it shows up in
/// startup profiles when done per log call) and Apple's guidance is to
/// create loggers once and reuse them.
fn macosLogger(comptime scope: @TypeOf(.EnumLiteral)) *macos.os.Log {
const S = struct {
var cached: std.atomic.Value(?*macos.os.Log) = .init(null);
};
if (S.cached.load(.acquire)) |v| return v;
// Create and attempt to store our logger. If we race with another
// thread then we use theirs and release ours.
const created = macos.os.Log.create(
build_config.bundle_id,
@tagName(scope),
);
if (S.cached.cmpxchgStrong(
null,
created,
.acq_rel,
.acquire,
)) |existing| {
created.release();
return existing.?;
}
return created;
}
pub const std_options: std.Options = .{
// Our log level is always at least info in every build mode.
//