Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ jobs:
uses: actions/checkout@v4

- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
uses: mlugg/setup-zig@v2
with:
version: 0.15.1
version: 0.16.0

- name: Display platform information
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
- uses: goto-bus-stop/setup-zig@v2
- uses: mlugg/setup-zig@v2
with:
cache: false
- name: Generate Docs
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/format-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
uses: actions/checkout@v4

- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
uses: mlugg/setup-zig@v2
with:
version: 0.15.1
version: 0.16.0

- name: Check code formatting
run: zig fmt --check .
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
# 架构配置标识符
arch_config: [linux-x86_64, linux-aarch64, macos-arm64, windows-x86_64]
# 定时任务只测试 master 版本,其他情况测试所有版本
version: ${{ github.event_name == 'schedule' && fromJSON('["master"]') || fromJSON('["0.14.1", "0.15.1", "master"]') }}
version: ${{ github.event_name == 'schedule' && fromJSON('["master"]') || fromJSON('["0.14.1", "0.15.1", "0.16.0", "master"]') }}
include:
# Linux x86_64
- arch_config: linux-x86_64
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
fetch-depth: 0

- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
uses: mlugg/setup-zig@v2
with:
version: ${{ matrix.version }}

Expand Down
3 changes: 1 addition & 2 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,7 @@ defer result.free(allocator); // 释放内存

### 5.1 支持的 Zig 版本

-**完全支持**:Zig 0.14.x, 0.15.x
- ⚠️ **部分支持**:Zig 0.16 (nightly)
-**完全支持**:Zig 0.14.x, 0.15.x, 0.16.0

### 5.2 关键差异点

Expand Down
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ zig build docs
```

### Zig Version Compatibility
- **Currently supports**: Zig 0.14.0 and 0.15.x
- **Partial support**: Zig 0.16 (nightly) - may have compatibility issues
- **Currently supports**: Zig 0.14.0, 0.15.x, and 0.16.0
- **Legacy support**: Zig 0.11-0.13 (use library version 0.0.6 for Zig 0.13 and older)
- Code uses version detection (`builtin.zig_version.minor`) to handle API differences:
- Endianness enum changes (`.Big`/`.Little` vs `.big`/`.little`)
- ArrayList API changes in Zig 0.15+ (allocator parameter required for methods)
- ArrayList initialization changes in Zig 0.16 (`std.ArrayList(T){}` removed, use `initCapacity`)
- Build system API differences between versions
- `std.time.Timer` removed in 0.16 (benchmarks use `std.Io.Clock.awake` instead)
- `std.heap.GeneralPurposeAllocator` removed in 0.16 (replaced with `page_allocator`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The claim that std.heap.GeneralPurposeAllocator is removed in Zig 0.16 is incorrect. It remains a core part of the standard library, although its internal implementation details or its location within std.heap may have evolved. Switching to page_allocator in benchmarks is a regression in measurement quality as it is significantly slower due to per-allocation system calls.

- `std.io.fixedBufferStream` removed in 0.16 (compat layer provides `BufferStream`)

## Architecture

Expand Down Expand Up @@ -79,4 +82,4 @@ Tests use Zig's built-in testing framework. The test suite in `src/test.zig` cov

## CI/CD

GitHub Actions workflow tests against multiple Zig versions (0.14.0, 0.15.1, nightly) to ensure compatibility.
GitHub Actions workflow tests against multiple Zig versions (0.14.1, 0.15.1, 0.16.0, and master) to ensure compatibility.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ This library is tested and optimized for all major platforms and architectures:
| 0.13 and older | 0.0.6 | Legacy support |
| 0.14.0 | Current | ✅ Fully supported |
| 0.15.x | Current | ✅ Fully supported |
| 0.16.0-dev (nightly) | Current | ✅ Supported with compatibility layer |
| 0.16.0 | Current | ✅ Supported with compatibility layer |

> **Note:** For Zig 0.13 and older versions, please use version `0.0.6` of this library.
> **Note:** Zig 0.16+ removes `std.io.FixedBufferStream`, but this library provides a compatibility layer to maintain the same API across all supported versions.

For Zig `0.14.0`, `0.15.x`, and `0.16.0-dev`, follow these steps:
For Zig `0.14.0`, `0.15.x`, and `0.16.0`, follow these steps:

1. **Add as a dependency:**
Add the library to your `build.zig.zon` file. You can fetch a specific commit or branch.
Expand Down
4 changes: 2 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ Zig 编程语言的 MessagePack 实现。此库提供了一种简单高效的方
| 0.13 及更早版本 | 0.0.6 | 旧版支持 |
| 0.14.0 | 当前版本 | ✅ 完全支持 |
| 0.15.x | 当前版本 | ✅ 完全支持 |
| 0.16.0-dev (nightly) | 当前版本 | ✅ 通过兼容层支持 |
| 0.16.0 | 当前版本 | ✅ 通过兼容层支持 |

> **注意**: 对于 Zig 0.13 及更早版本,请使用本库的 `0.0.6` 版本。
> **注意**: Zig 0.16+ 移除了 `std.io.FixedBufferStream`,但本库提供了兼容层以在所有支持的版本中维持相同的 API。

对于 Zig `0.14.0`、`0.15.x` 和 `0.16.0-dev` 版本,请按以下步骤操作:
对于 Zig `0.14.0`、`0.15.x` 和 `0.16.0` 版本,请按以下步骤操作:

1. **添加为依赖项**:
将库添加到您的 `build.zig.zon` 文件中。您可以获取特定的提交或分支。
Expand Down
42 changes: 36 additions & 6 deletions src/bench.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,45 @@ const pack = msgpack.Pack(
bufferType.read,
);

const is_zig_16 = builtin.zig_version.minor >= 16;
const BenchRuntime = if (is_zig_16) struct {
var io: ?std.Io = null;
} else struct {};

/// Get monotonic time in nanoseconds on Zig 0.16+.
fn getTimeNs() u64 {
if (is_zig_16) {
const io = BenchRuntime.io orelse @panic("benchmark runtime io is not initialized");
return @intCast(std.Io.Clock.awake.now(io).nanoseconds);
}

unreachable;
}
Comment on lines +25 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The manual implementation of getTimeNs introduces significant boilerplate and potential portability issues (e.g., dependency on libc for clock_gettime on non-Windows platforms). For Zig 0.16, the recommended replacement for std.time.Timer is std.time.Instant, which provides a cross-platform monotonic clock API. Additionally, the Windows implementation lacks a check for a zero frequency from RtlQueryPerformanceFrequency, which would cause a division by zero.


/// Benchmark timer helper
/// Run a benchmark and print results
fn benchmark(
comptime name: []const u8,
comptime iterations: usize,
comptime func: fn (allocator: std.mem.Allocator) anyerror!void,
) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const allocator = std.heap.page_allocator;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using std.heap.page_allocator for benchmarks is generally discouraged because it performs a system call for every allocation and deallocation, which can dominate the benchmark results and hide the actual performance characteristics of the code being tested. Since GeneralPurposeAllocator is still available in Zig 0.16, it should be used instead.


// Warmup
for (0..10) |_| {
try func(allocator);
}

// Actual benchmark
var timer = try std.time.Timer.start();
var legacy_timer: if (is_zig_16) void else std.time.Timer = if (is_zig_16) {} else undefined;
if (!is_zig_16) legacy_timer = try std.time.Timer.start();
const start_ns = if (is_zig_16) getTimeNs() else 0;
Comment on lines +49 to +51
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The conditional logic for handling the timer across Zig versions is quite clunky. Instead of manually implementing getTimeNs and using a void type for legacy_timer, it is recommended to use std.time.Instant for Zig 0.16. This would allow for a cleaner abstraction or a simple wrapper that works across all supported versions.


for (0..iterations) |_| {
try func(allocator);
}
const elapsed_ns = timer.read();

const elapsed_ns = if (is_zig_16) getTimeNs() - start_ns else legacy_timer.read();

const avg_ns = elapsed_ns / iterations;
const ops_per_sec = if (avg_ns > 0) (1_000_000_000 / avg_ns) else 0;
Expand Down Expand Up @@ -850,7 +867,7 @@ fn benchMixedTypesRead(allocator: std.mem.Allocator) !void {
// Main Benchmark Runner
// ============================================================================

pub fn main() !void {
fn runBenchmarks() !void {
std.debug.print("\n", .{});
std.debug.print("=" ** 80 ++ "\n", .{});
std.debug.print("MessagePack Benchmark Suite\n", .{});
Expand Down Expand Up @@ -936,3 +953,16 @@ pub fn main() !void {
std.debug.print("Benchmark Complete\n", .{});
std.debug.print("=" ** 80 ++ "\n", .{});
}

const BenchEntry = if (is_zig_16) struct {
pub fn main(init: std.process.Init) !void {
BenchRuntime.io = init.io;
try runBenchmarks();
}
} else struct {
pub fn main() !void {
try runBenchmarks();
}
};

pub const main = BenchEntry.main;
4 changes: 2 additions & 2 deletions src/msgpack.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ pub const Payload = union(enum) {
var new_heap = if (current_zig.minor == 14)
std.ArrayList(Payload).init(alloc)
else
std.ArrayList(Payload){};
std.ArrayList(Payload).initCapacity(alloc, 0) catch unreachable;
Comment on lines 1316 to +1319
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using initCapacity(alloc, 0) catch unreachable is suboptimal and introduces a potential crash point during memory cleanup. In Zig 0.16, std.ArrayList(T).init(allocator) is the preferred way to initialize a managed ArrayList as it does not perform an initial allocation and thus cannot fail. The logic can be simplified to use init for both 0.14 and 0.16, only using the struct literal for 0.15 where the ArrayList was unmanaged.

                    var new_heap = if (current_zig.minor == 15)
                        std.ArrayList(Payload){}
                    else
                        std.ArrayList(Payload).init(alloc);


// Copy existing items from stack buffer to heap
for (buffer[0..len.*]) |item| {
Expand Down Expand Up @@ -2857,7 +2857,7 @@ pub fn PackWithLimits(
var parse_stack = if (current_zig.minor == 14)
std.ArrayList(ParseState).init(allocator)
else
std.ArrayList(ParseState){};
try std.ArrayList(ParseState).initCapacity(allocator, 0);
Comment on lines 2857 to +2860
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the previous comment, std.ArrayList(T).init(allocator) should be used for Zig 0.14 and 0.16 to avoid the unnecessary try or catch unreachable on an operation that doesn't need to allocate memory initially.

            var parse_stack = if (current_zig.minor == 15)
                std.ArrayList(ParseState){}
            else
                std.ArrayList(ParseState).init(allocator);

defer if (current_zig.minor == 14) parse_stack.deinit() else parse_stack.deinit(allocator);
errdefer cleanupParseStack(&parse_stack, allocator);

Expand Down
Loading
Loading