diff --git a/.github/workflows/zig.yml b/.github/workflows/ci.yml similarity index 95% rename from .github/workflows/zig.yml rename to .github/workflows/ci.yml index 7bfc26e..be16a42 100644 --- a/.github/workflows/zig.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - run: zig build test --summary all lint: runs-on: ubuntu-latest diff --git a/bench/h264_sps.zig b/bench/h264_sps.zig index b71d12a..fc8a24d 100644 --- a/bench/h264_sps.zig +++ b/bench/h264_sps.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const zbench = @import("zbench"); const h264 = @import("media").h264; const sps_nal = [_]u8{ @@ -23,59 +24,31 @@ const sps_with_frame_cropping = [_]u8{ 0x20, }; -const iterations = 1_000_000; - -pub fn main() !void { - var buffer: [1024]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buffer); - - try stdout.interface.writeAll("\x1b[1;36m┌─────────────────────────┐\x1b[0m\n"); - try stdout.interface.writeAll("\x1b[1;36m│ H264 SPS Benchmarks │\x1b[0m\n"); - try stdout.interface.writeAll("\x1b[1;36m└─────────────────────────┘\x1b[0m\n\n"); - - // Warm-up: one pass to bring code/data into cache. - for (0..iterations) |_| { - const sps = try h264.Sps.parse(sps_nal[1..]); - std.mem.doNotOptimizeAway(sps); - } - - const fixtures = [_]struct { - name: []const u8, - data: []const u8, - }{ - .{ .name = "Basic SPS", .data = sps_nal[1..] }, - .{ .name = "SPS with scaling list", .data = sps_with_scaling_list[1..] }, - .{ .name = "SPS with frame cropping", .data = sps_with_frame_cropping[1..] }, - }; - - for (fixtures) |fixture| { - try benchMark(fixture.name, fixture.data, &stdout.interface); - } +fn benchBasicSps(allocator: std.mem.Allocator) void { + _ = allocator; + const sps = h264.Sps.parse(sps_nal[1..]) catch unreachable; + std.mem.doNotOptimizeAway(sps); +} - try stdout.interface.flush(); +fn benchScalingList(allocator: std.mem.Allocator) void { + _ = allocator; + const sps = h264.Sps.parse(sps_with_scaling_list[1..]) catch unreachable; + std.mem.doNotOptimizeAway(sps); } -fn benchMark(name: []const u8, data: []const u8, writer: *std.Io.Writer) !void { - var timer = try std.time.Timer.start(); +fn benchFrameCropping(allocator: std.mem.Allocator) void { + _ = allocator; + const sps = h264.Sps.parse(sps_with_frame_cropping[1..]) catch unreachable; + std.mem.doNotOptimizeAway(sps); +} - for (0..iterations) |_| { - const sps = try h264.Sps.parse(data); - std.mem.doNotOptimizeAway(sps); - } +pub fn main(init: std.process.Init) !void { + var bench = zbench.Benchmark.init(init.gpa, .{}); + defer bench.deinit(); - const elapsed_ns = timer.read(); - const ns_per_op = elapsed_ns / iterations; - const ops_per_sec = @as(u64, std.time.ns_per_s) / @max(ns_per_op, 1); + try bench.add("H264 Basic SPS", benchBasicSps, .{}); + try bench.add("H264 SPS with scaling list", benchScalingList, .{}); + try bench.add("H264 SPS with frame cropping", benchFrameCropping, .{}); - try writer.print("\x1b[1;33mH264 {s}\x1b[0m\n" ++ - " iterations : {d}\n" ++ - " total time : {d} ms\n" ++ - " ns/op : {d}\n" ++ - " ops/sec : {d}\n\n", .{ - name, - iterations, - elapsed_ns / std.time.ns_per_ms, - ns_per_op, - ops_per_sec, - }); + try bench.run(init.io, std.Io.File.stdout()); } diff --git a/build.zig b/build.zig index 510f769..fdcc329 100644 --- a/build.zig +++ b/build.zig @@ -17,10 +17,16 @@ pub fn build(b: *std.Build) void { test_step.dependOn(&run_media_tests.step); { + const zbench_dep = b.dependency("zbench", .{ + .target = target, + .optimize = .ReleaseFast, + }); + const zbench_mod = zbench_dep.module("zbench"); + const bench_step = b.step("bench", "Run all benchmarks"); const benches = .{ - .{ .name = "h264_sps", .src = "bench/core/h264_sps.zig" }, + .{ .name = "h264_sps", .src = "bench/h264_sps.zig" }, }; inline for (benches) |bench| { @@ -32,6 +38,7 @@ pub fn build(b: *std.Build) void { .optimize = .ReleaseFast, .imports = &.{ .{ .name = "media", .module = mod }, + .{ .name = "zbench", .module = zbench_mod }, }, }), }); diff --git a/build.zig.zon b/build.zig.zon index 249d156..ce1da8e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,8 +2,13 @@ .name = .media, .version = "0.1.0", .fingerprint = 0x6a2ca10c089dcfa9, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.2", - .dependencies = .{}, + .minimum_zig_version = "0.16.0", + .dependencies = .{ + .zbench = .{ + .url = "https://github.com/hendriknielaender/zBench/archive/zig-0.16.0.tar.gz", + .hash = "zbench-0.11.2-YTdc76Q_AQAxonIKZ2-H1PdcESJGwyuYylw6RkPiBqyx", + }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/src/buffer_pool_allocator.zig b/src/buffer_pool_allocator.zig index 2e6ba58..e0d8b70 100644 --- a/src/buffer_pool_allocator.zig +++ b/src/buffer_pool_allocator.zig @@ -11,7 +11,7 @@ const Bucket = struct { const Block = struct { next: ?*Block }; - fn init(allocator: std.mem.Allocator, block_size: usize, block_count: usize) !Bucket { + fn init(allocator: std.mem.Allocator, block_size: usize, block_count: usize) std.mem.Allocator.Error!Bucket { const total_size = block_size * block_count; const buffer = try allocator.alloc(u8, total_size); @@ -78,23 +78,18 @@ pub fn BufferPoolAllocator(comptime config: Config) type { return struct { const have_mutex = config.thread_safe; - const mutex_init = if (have_mutex) std.Thread.Mutex{} else DummyMutex{}; - - const DummyMutex = struct { - inline fn lock(_: DummyMutex) void {} - inline fn unlock(_: DummyMutex) void {} - }; + const Mutex = if (have_mutex) std.Io.Mutex else void; buckets: [config.bucket_sizes.len]Bucket, backing_allocator: std.mem.Allocator, buffer_ref_allocator: BufferRefAllocator, - mutex: @TypeOf(mutex_init) = mutex_init, + mutex: Mutex = if (have_mutex) std.Io.Mutex.init else {}, pub fn init(backing_allocator: std.mem.Allocator) !@This() { var self = @This(){ .backing_allocator = backing_allocator, .buckets = undefined, - .buffer_ref_allocator = BufferRefAllocator.init(backing_allocator), + .buffer_ref_allocator = BufferRefAllocator.empty, }; var initialized: usize = 0; errdefer { @@ -115,7 +110,7 @@ pub fn BufferPoolAllocator(comptime config: Config) type { for (0..config.bucket_sizes.len) |idx| { self.buckets[idx].deinit(self.backing_allocator); } - self.buffer_ref_allocator.deinit(); + self.buffer_ref_allocator.deinit(self.backing_allocator); } pub fn allocator(self: *@This()) std.mem.Allocator { @@ -133,9 +128,9 @@ pub fn BufferPoolAllocator(comptime config: Config) type { fn alloc(context: *anyopaque, len: usize, _: std.mem.Alignment, _: usize) ?[*]u8 { const self: *@This() = @ptrCast(@alignCast(context)); if (len == buffer_ref_size) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); - const buf_ref = self.buffer_ref_allocator.create() catch { + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); + const buf_ref = self.buffer_ref_allocator.create(self.backing_allocator) catch { return null; }; return @ptrCast(@alignCast(buf_ref)); @@ -143,8 +138,8 @@ pub fn BufferPoolAllocator(comptime config: Config) type { for (&self.buckets) |*bucket| { if (len <= bucket.block_size) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); if (bucket.acquire()) |b| { return b.ptr; @@ -157,8 +152,8 @@ pub fn BufferPoolAllocator(comptime config: Config) type { fn free(context: *anyopaque, memory: []u8, _: std.mem.Alignment, _: usize) void { const self: *@This() = @ptrCast(@alignCast(context)); if (memory.len == buffer_ref_size) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); self.buffer_ref_allocator.destroy(@ptrCast(@alignCast(memory.ptr))); return; } @@ -166,8 +161,9 @@ pub fn BufferPoolAllocator(comptime config: Config) type { for (&self.buckets) |*bucket| { const start = @intFromPtr(bucket.buffer.ptr); if (ptr >= start and ptr < start + bucket.buffer.len) { - if (have_mutex) std.Thread.Mutex.lock(&self.mutex); - defer if (have_mutex) std.Thread.Mutex.unlock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); + bucket.release(memory); return; } diff --git a/src/h264.zig b/src/h264.zig index f7d2161..870a456 100644 --- a/src/h264.zig +++ b/src/h264.zig @@ -163,11 +163,7 @@ pub const Sps = struct { const entries: usize = if (sps.chroma_format_idc != 3) 8 else 12; for (0..entries) |i| { if (try bit_reader.takeBit() == 0) continue; - if (i < 6) { - try parseScalingList(&bit_reader, 16); - } else { - try parseScalingList(&bit_reader, 64); - } + try parseScalingList(&bit_reader, if (i < 6) 16 else 64); } } }, diff --git a/src/io.zig b/src/io.zig index c19f28c..01a926e 100644 --- a/src/io.zig +++ b/src/io.zig @@ -207,5 +207,5 @@ pub const BitReader = struct { }; test { - std.testing.refAllDeclsRecursive(@This()); + std.testing.refAllDecls(@This()); } diff --git a/src/root.zig b/src/root.zig index 26cfe49..eeddced 100644 --- a/src/root.zig +++ b/src/root.zig @@ -18,7 +18,7 @@ const BufferRef = struct { .ref_count = .init(1), }; - fn init(buffer_ref: *BufferRef, allocator: Allocator, size: usize) !void { + fn init(buffer_ref: *BufferRef, allocator: Allocator, size: usize) Allocator.Error!void { buffer_ref.data = try allocator.alloc(u8, size); } @@ -78,7 +78,7 @@ pub const Packet = struct { /// Allocates an uninitialised owned buffer of `size` bytes. /// Use `mutableData()` to fill the buffer before sharing the packet. - pub fn alloc(allocator: Allocator, size: usize) !Packet { + pub fn alloc(allocator: Allocator, size: usize) Allocator.Error!Packet { const buffer_ref = try allocator.create(BufferRef); buffer_ref.* = .{ @@ -93,7 +93,7 @@ pub const Packet = struct { } /// Allocates an owned buffer and copies `src` into it (analogous to `std.mem.Allocator.dupe`). - pub fn dupe(allocator: Allocator, src: []const u8) !Packet { + pub fn dupe(allocator: Allocator, src: []const u8) Allocator.Error!Packet { var packet = try alloc(allocator, src.len); @memcpy(packet.mutableData().?, src); return packet; @@ -278,8 +278,5 @@ test "Packet.mutableData: writes are visible through data slice" { } test { - std.testing.refAllDeclsRecursive(@This()); - _ = @import("h264.zig"); - _ = @import("io.zig"); - _ = @import("buffer_pool_allocator.zig"); + std.testing.refAllDecls(@This()); }