├── .gitignore ├── .github └── workflows │ ├── format-check.yml │ ├── deploy_docs.yml │ ├── test.yml │ └── benchmark.yml ├── LICENSE ├── src ├── compat.zig └── bench.zig ├── CLAUDE.md ├── benchmark.md ├── AGENT.md ├── README_CN.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | zig-out 3 | .direnv 4 | .zig-cache 5 | -------------------------------------------------------------------------------- /.github/workflows/format-check.yml: -------------------------------------------------------------------------------- 1 | name: Code Format Check 2 | 3 | run-name: "🎨 Format Check · ${{ github.event_name == 'pull_request' && format('PR #{0}', github.event.pull_request.number) || github.ref_name }}" 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | 11 | jobs: 12 | format: 13 | name: Check Code Formatting 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Zig 20 | uses: goto-bus-stop/setup-zig@v2 21 | with: 22 | version: 0.15.1 23 | 24 | - name: Check code formatting 25 | run: zig fmt --check . 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Zig Chinese Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/compat.zig: -------------------------------------------------------------------------------- 1 | // Compatibility layer for different Zig versions 2 | const std = @import("std"); 3 | const builtin = @import("builtin"); 4 | const current_zig = builtin.zig_version; 5 | 6 | // BufferStream implementation for Zig 0.16+ 7 | // This mimics the behavior of the old FixedBufferStream 8 | pub const BufferStream = if (current_zig.minor >= 16) struct { 9 | buffer: []u8, 10 | pos: usize, 11 | 12 | const Self = @This(); 13 | 14 | pub const WriteError = error{NoSpaceLeft}; 15 | pub const ReadError = error{EndOfStream}; 16 | 17 | pub fn init(buffer: []u8) Self { 18 | return .{ 19 | .buffer = buffer, 20 | .pos = 0, 21 | }; 22 | } 23 | 24 | pub fn write(self: *Self, bytes: []const u8) WriteError!usize { 25 | const available = self.buffer.len - self.pos; 26 | if (bytes.len > available) return error.NoSpaceLeft; 27 | @memcpy(self.buffer[self.pos..][0..bytes.len], bytes); 28 | self.pos += bytes.len; 29 | return bytes.len; 30 | } 31 | 32 | pub fn read(self: *Self, dest: []u8) ReadError!usize { 33 | // Read from current position in buffer 34 | const available = self.buffer.len - self.pos; 35 | if (available == 0) return 0; 36 | 37 | const to_read = @min(dest.len, available); 38 | @memcpy(dest[0..to_read], self.buffer[self.pos..][0..to_read]); 39 | self.pos += to_read; 40 | return to_read; 41 | } 42 | 43 | pub fn reset(self: *Self) void { 44 | self.pos = 0; 45 | } 46 | 47 | pub fn seekTo(self: *Self, pos: usize) !void { 48 | if (pos > self.buffer.len) { 49 | return error.OutOfBounds; 50 | } 51 | self.pos = pos; 52 | } 53 | 54 | pub fn getPos(self: Self) usize { 55 | return self.pos; 56 | } 57 | 58 | pub fn getEndPos(self: Self) usize { 59 | return self.buffer.len; 60 | } 61 | } else std.io.FixedBufferStream([]u8); 62 | 63 | pub const fixedBufferStream = if (current_zig.minor >= 16) 64 | BufferStream.init 65 | else 66 | std.io.fixedBufferStream; 67 | -------------------------------------------------------------------------------- /.github/workflows/deploy_docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | run-name: "📚 Documentation · ${{ github.event_name == 'workflow_dispatch' && 'Manual Deploy' || github.event.workflow_run.head_branch }}" 4 | 5 | on: 6 | workflow_run: 7 | workflows: ["Unit Tests"] 8 | types: 9 | - completed 10 | branches: 11 | - main 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | inputs: 16 | logLevel: 17 | description: "Log level" 18 | required: true 19 | default: "warning" 20 | tags: 21 | description: "deploy docs" 22 | 23 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 24 | permissions: 25 | contents: write 26 | pages: write 27 | id-token: write 28 | 29 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 30 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 31 | concurrency: 32 | group: pages 33 | cancel-in-progress: false 34 | 35 | jobs: 36 | # Build job 37 | generate_docs: 38 | runs-on: ubuntu-latest 39 | # 只在 unit test 成功后或手动触发时执行 40 | if: | 41 | github.event_name == 'workflow_dispatch' || 42 | (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v3 46 | with: 47 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 48 | - uses: goto-bus-stop/setup-zig@v2 49 | with: 50 | cache: false 51 | - name: Generate Docs 52 | run: zig build docs 53 | - name: Upload artifact 54 | uses: actions/upload-pages-artifact@v3 55 | with: 56 | path: ./zig-out/docs 57 | deploy: 58 | environment: 59 | name: github-pages 60 | url: ${{ steps.deployment.outputs.page_url }} 61 | runs-on: ubuntu-latest 62 | needs: generate_docs 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | run-name: "🧪 Unit Tests · ${{ github.event_name == 'schedule' && 'Scheduled (master)' || (github.event_name == 'workflow_dispatch' && 'Manual Run' || github.event.workflow_run.head_branch) }}" 4 | 5 | on: 6 | schedule: 7 | # 每天凌晨 2 点执行 8 | - cron: "0 2 * * *" 9 | workflow_dispatch: 10 | workflow_run: 11 | workflows: ["Code Format Check"] 12 | types: 13 | - completed 14 | 15 | jobs: 16 | test: 17 | name: Unit Test (${{ matrix.os }} - ${{ matrix.arch }} - Zig ${{ matrix.version }}) 18 | # 依赖 format check 成功,或定时任务/手动触发时执行 19 | if: | 20 | github.event_name == 'schedule' || 21 | github.event_name == 'workflow_dispatch' || 22 | (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') 23 | strategy: 24 | matrix: 25 | # 架构配置标识符 26 | arch_config: [linux-x86_64, linux-aarch64, macos-arm64, windows-x86_64] 27 | # 定时任务只测试 master 版本,其他情况测试所有版本 28 | version: ${{ github.event_name == 'schedule' && fromJSON('["master"]') || fromJSON('["0.14.1", "0.15.1", "master"]') }} 29 | include: 30 | # Linux x86_64 31 | - arch_config: linux-x86_64 32 | os: ubuntu-latest 33 | arch: x86_64 34 | runner: ubuntu-latest 35 | # Linux aarch64 36 | - arch_config: linux-aarch64 37 | os: ubuntu-latest 38 | arch: aarch64 39 | runner: ubuntu-24.04-arm 40 | # macOS arm64 41 | - arch_config: macos-arm64 42 | os: macos-latest 43 | arch: arm64 44 | runner: macos-14 45 | # Windows x86_64 46 | - arch_config: windows-x86_64 47 | os: windows-latest 48 | arch: x86_64 49 | runner: windows-latest 50 | fail-fast: false 51 | runs-on: ${{ matrix.runner }} 52 | steps: 53 | - name: Checkout code 54 | uses: actions/checkout@v4 55 | with: 56 | fetch-depth: 0 57 | 58 | - name: Setup Zig 59 | uses: goto-bus-stop/setup-zig@v2 60 | with: 61 | version: ${{ matrix.version }} 62 | 63 | - name: Run unit tests 64 | run: zig build test --summary all 65 | 66 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | This is a MessagePack implementation library for Zig, providing serialization and deserialization capabilities with full MessagePack specification support including timestamp extensions. 8 | 9 | ## Development Commands 10 | 11 | ### Build and Test 12 | ```bash 13 | # Run all unit tests 14 | zig build test 15 | 16 | # Run tests with detailed output 17 | zig build test --summary all 18 | 19 | # Generate documentation 20 | zig build docs 21 | ``` 22 | 23 | ### Zig Version Compatibility 24 | - **Currently supports**: Zig 0.14.0 and 0.15.x 25 | - **Partial support**: Zig 0.16 (nightly) - may have compatibility issues 26 | - **Legacy support**: Zig 0.11-0.13 (use library version 0.0.6 for Zig 0.13 and older) 27 | - Code uses version detection (`builtin.zig_version.minor`) to handle API differences: 28 | - Endianness enum changes (`.Big`/`.Little` vs `.big`/`.little`) 29 | - ArrayList API changes in Zig 0.15+ (allocator parameter required for methods) 30 | - Build system API differences between versions 31 | 32 | ## Architecture 33 | 34 | ### Core Structure 35 | 36 | The library consists of three main files: 37 | - `src/msgpack.zig`: Core implementation with Pack/Unpack functionality and Payload type system 38 | - `src/test.zig`: Comprehensive test suite 39 | - `build.zig`: Build configuration with version compatibility handling 40 | 41 | ### Key Components 42 | 43 | **Payload Union Type**: Central data representation supporting all MessagePack types: 44 | - Basic types: nil, bool, int, uint, float 45 | - Container types: array, map 46 | - Binary types: str, bin, ext 47 | - Special type: timestamp (extension type -1) 48 | 49 | **Pack Generic Function**: Template-based packer/unpacker that works with any Read/Write context: 50 | - Handles endianness conversion (MessagePack uses big-endian) 51 | - Supports streaming serialization/deserialization 52 | - Memory efficient with configurable allocators 53 | 54 | **Type Wrappers**: Special wrapper types for structured data: 55 | - `Str`, `Bin`, `EXT`, `Timestamp` - provide type safety and convenience methods 56 | - `wrapStr()`, `wrapBin()`, `wrapEXT()` - helper functions for creating wrapped types 57 | 58 | ### MessagePack Format Implementation 59 | 60 | The library implements the complete MessagePack specification: 61 | - Fixed-size formats for small integers and strings 62 | - Variable-size formats with size prefixes 63 | - Extension type system with timestamp support (type -1) 64 | - Proper handling of signed/unsigned integer boundaries 65 | 66 | ### Memory Management 67 | 68 | - All dynamic allocations go through provided Allocator 69 | - Payload types that allocate memory must be freed with `payload.free(allocator)` 70 | - Container types (array, map) manage their child elements' memory 71 | 72 | ## Testing Approach 73 | 74 | Tests use Zig's built-in testing framework. The test suite in `src/test.zig` covers: 75 | - All MessagePack type encodings/decodings 76 | - Edge cases and boundary conditions 77 | - Timestamp format variations (32-bit, 64-bit, 96-bit) 78 | - Round-trip serialization verification 79 | 80 | ## CI/CD 81 | 82 | GitHub Actions workflow tests against multiple Zig versions (0.14.0, 0.15.1, nightly) to ensure compatibility. -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Performance Benchmark 2 | 3 | run-name: "⚡ Benchmark · ${{ github.event_name == 'workflow_dispatch' && 'Manual Run' || github.event.workflow_run.head_branch }}" 4 | 5 | on: 6 | workflow_run: 7 | workflows: ["Unit Tests"] 8 | types: 9 | - completed 10 | workflow_dispatch: 11 | 12 | jobs: 13 | benchmark: 14 | name: Run Benchmarks 15 | # 依赖 unit test 成功,或手动触发 16 | if: | 17 | github.event_name == 'workflow_dispatch' || 18 | (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest, macos-latest, windows-latest] 22 | fail-fast: false 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v4 27 | 28 | - name: Setup Zig 29 | uses: goto-bus-stop/setup-zig@v2 30 | with: 31 | version: 0.15.1 32 | 33 | - name: Display platform information 34 | shell: bash 35 | run: | 36 | echo "================================" 37 | echo "Platform Configuration" 38 | echo "================================" 39 | echo "OS: ${{ runner.os }}" 40 | echo "Runner: ${{ matrix.os }}" 41 | echo "Architecture: $(uname -m)" 42 | if [ "$RUNNER_OS" == "Linux" ]; then 43 | echo "Kernel: $(uname -r)" 44 | echo "Distribution: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2)" 45 | echo "CPU: $(lscpu | grep 'Model name' | cut -d':' -f2 | xargs)" 46 | echo "Total Memory: $(free -h | grep Mem | awk '{print $2}')" 47 | elif [ "$RUNNER_OS" == "macOS" ]; then 48 | echo "Kernel: $(uname -r)" 49 | echo "CPU: $(sysctl -n machdep.cpu.brand_string)" 50 | echo "Total Memory: $(sysctl -n hw.memsize | awk '{print $1/1024/1024/1024 " GB"}')" 51 | elif [ "$RUNNER_OS" == "Windows" ]; then 52 | echo "Kernel: $(uname -r)" 53 | echo "CPU: $(wmic cpu get name | sed -n 2p)" 54 | echo "Total Memory: $(wmic computersystem get totalphysicalmemory | sed -n 2p | awk '{print $1/1024/1024/1024 " GB"}')" 55 | fi 56 | echo "================================" 57 | echo "" 58 | 59 | - name: Build benchmark binary 60 | run: zig build -Doptimize=ReleaseFast 61 | 62 | - name: Run benchmark 63 | shell: bash 64 | run: | 65 | if [ "$RUNNER_OS" == "Windows" ]; then 66 | zig-out/bin/msgpack-bench.exe 67 | else 68 | zig-out/bin/msgpack-bench 69 | fi 70 | 71 | - name: Upload benchmark binary 72 | uses: actions/upload-artifact@v4 73 | with: 74 | name: msgpack-bench-${{ runner.os }}-${{ github.sha }} 75 | path: | 76 | zig-out/bin/msgpack-bench 77 | zig-out/bin/msgpack-bench.exe 78 | retention-days: 7 79 | if-no-files-found: ignore 80 | 81 | -------------------------------------------------------------------------------- /benchmark.md: -------------------------------------------------------------------------------- 1 | ```sh 2 | ❯ zig build bench 3 | 4 | ================================================================================ 5 | MessagePack Benchmark Suite 6 | ================================================================================ 7 | 8 | Basic Types: 9 | -------------------------------------------------------------------------------- 10 | Nil Write | 1000000 iterations | 23 ns/op | 43478260 ops/sec 11 | Nil Read | 1000000 iterations | 12484 ns/op | 80102 ops/sec 12 | Bool Write | 1000000 iterations | 27 ns/op | 37037037 ops/sec 13 | Bool Read | 1000000 iterations | 11992 ns/op | 83388 ops/sec 14 | Small Int Write | 1000000 iterations | 27 ns/op | 37037037 ops/sec 15 | Small Int Read | 1000000 iterations | 12429 ns/op | 80456 ops/sec 16 | Large Int Write | 1000000 iterations | 48 ns/op | 20833333 ops/sec 17 | Large Int Read | 1000000 iterations | 11975 ns/op | 83507 ops/sec 18 | Float Write | 1000000 iterations | 46 ns/op | 21739130 ops/sec 19 | Float Read | 1000000 iterations | 12382 ns/op | 80762 ops/sec 20 | 21 | Strings: 22 | -------------------------------------------------------------------------------- 23 | Short String Write (5 bytes) | 500000 iterations | 21283 ns/op | 46985 ops/sec 24 | Short String Read (5 bytes) | 500000 iterations | 38483 ns/op | 25985 ops/sec 25 | Medium String Write (~300 bytes) | 100000 iterations | 26060 ns/op | 38372 ops/sec 26 | Medium String Read (~300 bytes) | 100000 iterations | 40271 ns/op | 24831 ops/sec 27 | 28 | Binary Data: 29 | -------------------------------------------------------------------------------- 30 | Small Binary Write (32 bytes) | 500000 iterations | 23217 ns/op | 43071 ops/sec 31 | Small Binary Read (32 bytes) | 500000 iterations | 39784 ns/op | 25135 ops/sec 32 | Large Binary Write (1KB) | 100000 iterations | 33540 ns/op | 29815 ops/sec 33 | Large Binary Read (1KB) | 100000 iterations | 49550 ns/op | 20181 ops/sec 34 | 35 | Arrays: 36 | -------------------------------------------------------------------------------- 37 | Small Array Write (10 elements) | 100000 iterations | 56802 ns/op | 17605 ops/sec 38 | Small Array Read (10 elements) | 100000 iterations | 120598 ns/op | 8292 ops/sec 39 | Medium Array Write (100 elements) | 50000 iterations | 86305 ns/op | 11586 ops/sec 40 | Medium Array Read (100 elements) | 50000 iterations | 179349 ns/op | 5575 ops/sec 41 | 42 | Maps: 43 | -------------------------------------------------------------------------------- 44 | Small Map Write (10 entries) | 100000 iterations | 303730 ns/op | 3292 ops/sec 45 | Small Map Read (10 entries) | 100000 iterations | 450047 ns/op | 2221 ops/sec 46 | Medium Map Write (50 entries) | 50000 iterations | 942602 ns/op | 1060 ops/sec 47 | Medium Map Read (50 entries) | 50000 iterations | 1456101 ns/op | 686 ops/sec 48 | 49 | Extension Types: 50 | -------------------------------------------------------------------------------- 51 | EXT Write (16 bytes) | 500000 iterations | 24203 ns/op | 41317 ops/sec 52 | EXT Read (16 bytes) | 500000 iterations | 40463 ns/op | 24713 ops/sec 53 | 54 | Timestamps: 55 | -------------------------------------------------------------------------------- 56 | Timestamp32 Write | 1000000 iterations | 74 ns/op | 13513513 ops/sec 57 | Timestamp32 Read | 1000000 iterations | 12940 ns/op | 77279 ops/sec 58 | Timestamp64 Write | 1000000 iterations | 74 ns/op | 13513513 ops/sec 59 | Timestamp64 Read | 1000000 iterations | 12427 ns/op | 80469 ops/sec 60 | 61 | Complex Structures: 62 | -------------------------------------------------------------------------------- 63 | Nested Structure Write | 50000 iterations | 115840 ns/op | 8632 ops/sec 64 | Nested Structure Read | 50000 iterations | 239723 ns/op | 4171 ops/sec 65 | Mixed Types Write | 50000 iterations | 88667 ns/op | 11278 ops/sec 66 | Mixed Types Read | 50000 iterations | 185971 ns/op | 5377 ops/sec 67 | 68 | ================================================================================ 69 | Benchmark Complete 70 | ================================================================================ 71 | ❯ zig build bench -Doptimize=ReleaseFast 72 | 73 | ================================================================================ 74 | MessagePack Benchmark Suite 75 | ================================================================================ 76 | 77 | Basic Types: 78 | -------------------------------------------------------------------------------- 79 | Nil Write | 1000000 iterations | 6 ns/op | 166666666 ops/sec 80 | Nil Read | 1000000 iterations | 4359 ns/op | 229410 ops/sec 81 | Bool Write | 1000000 iterations | 2 ns/op | 500000000 ops/sec 82 | Bool Read | 1000000 iterations | 4635 ns/op | 215749 ops/sec 83 | Small Int Write | 1000000 iterations | 7 ns/op | 142857142 ops/sec 84 | Small Int Read | 1000000 iterations | 4710 ns/op | 212314 ops/sec 85 | Large Int Write | 1000000 iterations | 5 ns/op | 200000000 ops/sec 86 | Large Int Read | 1000000 iterations | 4978 ns/op | 200883 ops/sec 87 | Float Write | 1000000 iterations | 4 ns/op | 250000000 ops/sec 88 | Float Read | 1000000 iterations | 4487 ns/op | 222866 ops/sec 89 | 90 | Strings: 91 | -------------------------------------------------------------------------------- 92 | Short String Write (5 bytes) | 500000 iterations | 6876 ns/op | 145433 ops/sec 93 | Short String Read (5 bytes) | 500000 iterations | 9888 ns/op | 101132 ops/sec 94 | Medium String Write (~300 bytes) | 100000 iterations | 10189 ns/op | 98145 ops/sec 95 | Medium String Read (~300 bytes) | 100000 iterations | 14305 ns/op | 69905 ops/sec 96 | 97 | Binary Data: 98 | -------------------------------------------------------------------------------- 99 | Small Binary Write (32 bytes) | 500000 iterations | 9787 ns/op | 102176 ops/sec 100 | Small Binary Read (32 bytes) | 500000 iterations | 9506 ns/op | 105196 ops/sec 101 | Large Binary Write (1KB) | 100000 iterations | 6748 ns/op | 148192 ops/sec 102 | Large Binary Read (1KB) | 100000 iterations | 8847 ns/op | 113032 ops/sec 103 | 104 | Arrays: 105 | -------------------------------------------------------------------------------- 106 | Small Array Write (10 elements) | 100000 iterations | 8685 ns/op | 115141 ops/sec 107 | Small Array Read (10 elements) | 100000 iterations | 15166 ns/op | 65936 ops/sec 108 | Medium Array Write (100 elements) | 50000 iterations | 16765 ns/op | 59648 ops/sec 109 | Medium Array Read (100 elements) | 50000 iterations | 29700 ns/op | 33670 ops/sec 110 | 111 | Maps: 112 | -------------------------------------------------------------------------------- 113 | Small Map Write (10 entries) | 100000 iterations | 35803 ns/op | 27930 ops/sec 114 | Small Map Read (10 entries) | 100000 iterations | 44254 ns/op | 22596 ops/sec 115 | Medium Map Write (50 entries) | 50000 iterations | 48868 ns/op | 20463 ops/sec 116 | Medium Map Read (50 entries) | 50000 iterations | 68684 ns/op | 14559 ops/sec 117 | 118 | Extension Types: 119 | -------------------------------------------------------------------------------- 120 | EXT Write (16 bytes) | 500000 iterations | 6597 ns/op | 151584 ops/sec 121 | EXT Read (16 bytes) | 500000 iterations | 8930 ns/op | 111982 ops/sec 122 | 123 | Timestamps: 124 | -------------------------------------------------------------------------------- 125 | Timestamp32 Write | 1000000 iterations | 5 ns/op | 200000000 ops/sec 126 | Timestamp32 Read | 1000000 iterations | 4397 ns/op | 227427 ops/sec 127 | Timestamp64 Write | 1000000 iterations | 5 ns/op | 200000000 ops/sec 128 | Timestamp64 Read | 1000000 iterations | 4289 ns/op | 233154 ops/sec 129 | 130 | Complex Structures: 131 | -------------------------------------------------------------------------------- 132 | Nested Structure Write | 50000 iterations | 13055 ns/op | 76599 ops/sec 133 | Nested Structure Read | 50000 iterations | 15526 ns/op | 64408 ops/sec 134 | Mixed Types Write | 50000 iterations | 13108 ns/op | 76289 ops/sec 135 | Mixed Types Read | 50000 iterations | 18246 ns/op | 54806 ops/sec 136 | 137 | ================================================================================ 138 | Benchmark Complete 139 | ================================================================================ 140 | ``` 141 | 142 | -------------------------------------------------------------------------------- /AGENT.md: -------------------------------------------------------------------------------- 1 | # AGENT.md - LLM 代码阅读规范 2 | 3 | 本文档为 AI 助手提供 zig-msgpack 项目的结构化指南,便于理解代码库并进行开发协助。 4 | 5 | ## 文档目的 6 | 7 | - 为 LLM 提供项目快速索引 8 | - 规范代码理解和修改流程 9 | - 定义关键概念和术语 10 | - 说明代码规约和最佳实践 11 | 12 | --- 13 | 14 | ## 1. 项目核心概念 15 | 16 | ### 1.1 项目定位 17 | - **类型**:Zig 语言的 MessagePack 序列化/反序列化库 18 | - **规范**:完整实现 MessagePack specification (https://msgpack.org) 19 | - **特性**:支持所有 MessagePack 类型,包括 timestamp 扩展类型 (-1) 20 | 21 | ### 1.2 MessagePack 类型系统映射 22 | 23 | | MessagePack 类型 | Zig 实现 | 说明 | 24 | |-----------------|---------|------| 25 | | nil | `Payload.nil: void` | 空值 | 26 | | bool | `Payload.bool: bool` | 布尔值 | 27 | | int | `Payload.int: i64` | 有符号整数 | 28 | | uint | `Payload.uint: u64` | 无符号整数 | 29 | | float | `Payload.float: f64` | 浮点数 | 30 | | str | `Payload.str: Str` | UTF-8 字符串 | 31 | | bin | `Payload.bin: Bin` | 二进制数据 | 32 | | array | `Payload.arr: []Payload` | 数组 | 33 | | map | `Payload.map: Map` | 键值对 | 34 | | ext | `Payload.ext: EXT` | 扩展类型 | 35 | | timestamp | `Payload.timestamp: Timestamp` | 时间戳(ext type -1) | 36 | 37 | --- 38 | 39 | ## 2. 文件结构 40 | 41 | ``` 42 | src/ 43 | ├── msgpack.zig # 核心实现(主文件) 44 | ├── test.zig # 完整测试套件 45 | ├── bench.zig # 性能基准测试 46 | └── compat.zig # 跨版本兼容层 47 | ``` 48 | 49 | ### 2.1 各文件职责 50 | 51 | #### `src/msgpack.zig` (核心实现) 52 | - 定义 `Payload` 联合类型 53 | - 实现 `Pack()` 泛型序列化器 54 | - 提供包装类型:`Str`, `Bin`, `EXT`, `Timestamp` 55 | - 导出工具函数:`wrapStr()`, `wrapBin()`, `wrapEXT()` 56 | - 导出常量结构体:`FixLimits`, `IntBounds`, `FixExtLen`, `TimestampExt`, `MarkerBase` 57 | 58 | #### `src/compat.zig` (兼容层) 59 | - 提供 `BufferStream` 跨版本实现 60 | - 处理 Zig 0.14-0.16 API 差异 61 | - 导出 `fixedBufferStream` 兼容函数 62 | 63 | #### `src/test.zig` (测试套件) 64 | - 覆盖所有 MessagePack 类型 65 | - 测试边界条件和错误处理 66 | - 验证格式选择逻辑(最小编码原则) 67 | - 包含 Fuzz 测试覆盖随机数据 68 | 69 | #### `src/bench.zig` (性能基准测试) 70 | - 基本类型序列化/反序列化性能测试 71 | - 不同大小容器(数组/Map)的性能对比 72 | - 嵌套结构和混合类型的实际场景测试 73 | - 提供详细的吞吐量和延迟指标 74 | 75 | --- 76 | 77 | ## 3. 核心 API 规范 78 | 79 | ### 3.0 常量组织 80 | 81 | ### 9.4 性能优化指南 82 | 83 | 1. **使用内联函数**:频繁调用的小函数添加 `inline` 关键字 84 | 2. **利用泛型**:避免为每个类型重复相似代码 85 | 3. **使用 switch**:比 if-else 链更高效(编译器可优化为跳转表) 86 | 4. **减少分支**:简化控制流,提升分支预测准确性 87 | 5. **复用辅助函数**:如 `writeIntRaw`, `readIntRaw`, `writeDataWithLength` 88 | 库提供了组织化的常量结构体,方便使用和理解: 89 | 90 | ```zig 91 | // MessagePack 格式限制 92 | msgpack.FixLimits.POSITIVE_INT_MAX // 127 93 | msgpack.FixLimits.STR_LEN_MAX // 31 94 | msgpack.FixLimits.ARRAY_LEN_MAX // 15 95 | msgpack.FixLimits.MAP_LEN_MAX // 15 96 | 97 | // 整数类型边界 98 | msgpack.IntBounds.UINT8_MAX // 0xff 99 | msgpack.IntBounds.UINT16_MAX // 0xffff 100 | msgpack.IntBounds.INT8_MIN // -128 101 | 102 | // 固定扩展类型长度 103 | msgpack.FixExtLen.EXT4 // 4 104 | msgpack.FixExtLen.EXT8 // 8 105 | 106 | // Timestamp 相关常量 107 | msgpack.TimestampExt.TYPE_ID // -1 108 | msgpack.TimestampExt.FORMAT32_LEN // 4 109 | msgpack.TimestampExt.NANOSECONDS_MAX // 999_999_999 110 | ``` 111 | 112 | ### 3.1 Payload 创建方法 113 | 114 | #### 基本类型(栈分配,无需 free) 115 | ```zig 116 | Payload.nilToPayload() -> Payload 117 | Payload.boolToPayload(val: bool) -> Payload 118 | Payload.intToPayload(val: i64) -> Payload 119 | Payload.uintToPayload(val: u64) -> Payload 120 | Payload.floatToPayload(val: f64) -> Payload 121 | Payload.timestampFromSeconds(seconds: i64) -> Payload 122 | Payload.timestampToPayload(seconds: i64, nanoseconds: u32) -> Payload 123 | ``` 124 | 125 | #### 堆分配类型(需要 `payload.free(allocator)`) 126 | ```zig 127 | Payload.strToPayload(val: []const u8, allocator: Allocator) !Payload 128 | Payload.binToPayload(val: []const u8, allocator: Allocator) !Payload 129 | Payload.extToPayload(t: i8, data: []const u8, allocator: Allocator) !Payload 130 | Payload.arrPayload(len: usize, allocator: Allocator) !Payload 131 | Payload.mapPayload(allocator: Allocator) Payload 132 | ``` 133 | 134 | ### 3.2 Payload 操作方法 135 | 136 | #### 数组操作 137 | ```zig 138 | payload.getArrLen() !usize // 获取数组长度 139 | payload.getArrElement(index: usize) !Payload // 获取元素 140 | payload.setArrElement(index: usize, val: Payload) !void // 设置元素 141 | ``` 142 | 143 | #### Map 操作 144 | ```zig 145 | payload.mapGet(key: []const u8) !?Payload // 获取值(可能为 null) 146 | payload.mapPut(key: []const u8, val: Payload) !void // 插入/更新键值对 147 | ``` 148 | 149 | #### 类型转换 150 | ```zig 151 | // 宽松转换(允许类型转换) 152 | payload.getInt() !i64 // uint 可转换为 i64(如果在范围内) 153 | payload.getUint() !u64 // 正数 int 可转换为 u64 154 | 155 | // 严格转换(不允许类型转换) 156 | payload.asInt() !i64 // 只接受 .int 类型 157 | payload.asUint() !u64 // 只接受 .uint 类型 158 | payload.asFloat() !f64 // 只接受 .float 类型 159 | payload.asBool() !bool // 只接受 .bool 类型 160 | payload.asStr() ![]const u8 // 只接受 .str 类型 161 | payload.asBin() ![]u8 // 只接受 .bin 类型 162 | 163 | // 类型检查 164 | payload.isNil() bool // 检查是否为 nil 165 | payload.isNumber() bool // 检查是否为数字(int/uint/float) 166 | payload.isInteger() bool // 检查是否为整数(int/uint) 167 | ``` 168 | 169 | ### 3.3 序列化/反序列化 170 | 171 | #### 创建 Pack 实例 172 | ```zig 173 | const pack = msgpack.Pack( 174 | *BufferStream, // WriteContext 类型 175 | *BufferStream, // ReadContext 类型 176 | BufferStream.WriteError, 177 | BufferStream.ReadError, 178 | BufferStream.write, // writeFn 179 | BufferStream.read, // readFn 180 | ); 181 | 182 | var p = pack.init(&write_buffer, &read_buffer); 183 | ``` 184 | 185 | #### 基本操作 186 | ```zig 187 | try p.write(payload); // 序列化 188 | const result = try p.read(allocator); // 反序列化 189 | defer result.free(allocator); // 释放内存 190 | ``` 191 | 192 | --- 193 | 194 | ## 4. 编码规范 195 | 196 | ### 4.1 最小编码原则 197 | 198 | 序列化器**必须**使用最小格式: 199 | 200 | | 值范围 | 格式选择 | 201 | |--------|---------| 202 | | 0-127 | positive fixint (1 byte) | 203 | | -32 to -1 | negative fixint (1 byte) | 204 | | 128-255 | uint8 (2 bytes) | 205 | | 字符串 0-31 字节 | fixstr | 206 | | 数组 0-15 元素 | fixarray | 207 | | Map 0-15 条目 | fixmap | 208 | 209 | ### 4.2 Timestamp 格式选择 210 | 211 | ```zig 212 | // timestamp 32: nanoseconds == 0 && seconds 在 [0, 2^32-1] 213 | // 格式: fixext4 + type(-1) + 4 bytes seconds 214 | 215 | // timestamp 64: seconds 在 [0, 2^34-1] && nanoseconds <= 999999999 216 | // 格式: fixext8 + type(-1) + 8 bytes (nano<<34 | seconds) 217 | 218 | // timestamp 96: 其他情况(负秒数或大秒数) 219 | // 格式: ext8 + len(12) + type(-1) + 4 bytes nano + 8 bytes seconds 220 | ``` 221 | 222 | ### 4.3 字节序 223 | 224 | - **MessagePack 规范**:大端序(Big Endian) 225 | - **实现方式**:`std.mem.writeInt(T, buffer, value, .big)` 226 | 227 | --- 228 | 229 | ## 5. 版本兼容性处理 230 | 231 | ### 5.1 支持的 Zig 版本 232 | 233 | - ✅ **完全支持**:Zig 0.14.x, 0.15.x 234 | - ⚠️ **部分支持**:Zig 0.16 (nightly) 235 | 236 | ### 5.2 关键差异点 237 | 238 | #### Endianness 枚举 239 | ```zig 240 | // Zig 0.14-0.15 241 | std.builtin.Endian.big 242 | 243 | // Zig 0.16+ 244 | std.builtin.Endian.Big // 注意大小写变化 245 | ``` 246 | 247 | #### ArrayList API 248 | ```zig 249 | // Zig 0.14 250 | var list = ArrayList(T).init(allocator); 251 | try list.append(item); 252 | list.deinit(); 253 | 254 | // Zig 0.15+ 255 | var list = ArrayList(T){}; // 或 init(allocator) 256 | try list.append(allocator, item); // 需要传递 allocator 257 | list.deinit(allocator); 258 | ``` 259 | 260 | #### BufferStream 261 | ```zig 262 | // Zig 0.14-0.15 263 | std.io.FixedBufferStream([]u8) 264 | std.io.fixedBufferStream(buffer) 265 | 266 | // Zig 0.16+ 267 | 自定义 compat.BufferStream 实现 268 | compat.fixedBufferStream(buffer) 269 | ``` 270 | 271 | ### 5.3 版本检测模式 272 | 273 | ```zig 274 | const current_zig = builtin.zig_version; 275 | 276 | if (current_zig.minor >= 16) { 277 | // Zig 0.16+ 代码 278 | } else if (current_zig.minor == 15) { 279 | // Zig 0.15 代码 280 | } else { 281 | // Zig 0.14 代码 282 | } 283 | ``` 284 | 285 | --- 286 | 287 | ## 6. 内存管理规范 288 | 289 | ### 6.1 分配规则 290 | 291 | #### 需要分配的类型 292 | - `str`: 复制输入字符串 293 | - `bin`: 复制输入二进制数据 294 | - `ext`: 复制扩展数据 295 | - `arr`: 分配 `[]Payload` 切片 296 | - `map`: 分配 `StringHashMap` 和键字符串 297 | 298 | #### 不需要分配的类型 299 | - `nil`, `bool`, `int`, `uint`, `float`, `timestamp` 300 | 301 | ### 6.2 释放规则 302 | 303 | ```zig 304 | // 单个 Payload 305 | defer payload.free(allocator); 306 | 307 | // 嵌套结构会递归释放 308 | // 例如:Map 中的所有值,Array 中的所有元素 309 | ``` 310 | 311 | ### 6.3 错误处理中的内存管理 312 | 313 | ```zig 314 | const str_payload = try Payload.strToPayload(data, allocator); 315 | errdefer str_payload.free(allocator); // 后续失败时自动清理 316 | 317 | try some_operation(str_payload); 318 | ``` 319 | 320 | --- 321 | 322 | ## 7. 错误类型 323 | 324 | ### 7.1 MsgPackError 枚举 325 | 326 | ```zig 327 | error { 328 | StrDataLengthTooLong, // 字符串超过格式限制 329 | BinDataLengthTooLong, // 二进制数据超长 330 | ArrayLengthTooLong, // 数组超长 331 | MapLengthTooLong, // Map 超长 332 | InputValueTooLarge, // 输入值超出范围 333 | TypeMarkerReading, // 类型标记读取错误 334 | DataReading, // 数据读取错误 335 | LengthReading, // 长度读取错误 336 | ExtTypeLength, // 扩展类型长度不匹配 337 | InvalidType, // 类型不匹配/无效 338 | // ... 其他错误 339 | } 340 | ``` 341 | 342 | ### 7.2 Payload.Error 枚举 343 | 344 | ```zig 345 | error { 346 | NotMap, // 不是 Map 类型 347 | NotArray, // 不是 Array 类型 348 | } 349 | ``` 350 | 351 | --- 352 | 353 | ## 8. 测试指南 354 | 355 | ### 8.1 运行测试 356 | 357 | ```bash 358 | # 运行所有测试 359 | zig build test 360 | 361 | # 运行性能基准测试 362 | zig build bench 363 | 364 | # 使用 Release 模式运行以获得准确性能数据 365 | zig build bench -Doptimize=ReleaseFast 366 | 367 | # 详细输出 368 | zig build test --summary all 369 | ``` 370 | 371 | ### 8.2 测试覆盖范围 372 | 373 | - ✅ 所有 MessagePack 类型编码/解码 374 | - ✅ 边界值测试(fixint, fixstr, fixarray, fixmap) 375 | - ✅ 格式选择逻辑(8/16/32 位变体) 376 | - ✅ Unicode 字符串处理 377 | - ✅ 深度嵌套结构 378 | - ✅ 错误条件和异常处理 379 | - ✅ 内存泄漏验证(通过 testing.allocator) 380 | - ✅ Timestamp 三种格式(32/64/96 位) 381 | 382 | ### 8.3 测试模式 383 | 384 | ```zig 385 | test "描述性测试名称" { 386 | // 1. 准备缓冲区 387 | var arr: [size]u8 = std.mem.zeroes([size]u8); 388 | var write_buffer = fixedBufferStream(&arr); 389 | var read_buffer = fixedBufferStream(&arr); 390 | var p = pack.init(&write_buffer, &read_buffer); 391 | 392 | // 2. 写入数据 393 | try p.write(payload); 394 | 395 | // 3. 读取验证 396 | const result = try p.read(allocator); 397 | defer result.free(allocator); 398 | try expect(result.xxx == expected); 399 | } 400 | ``` 401 | 402 | ### 8.4 基准测试 403 | 404 | ```bash 405 | # 运行所有基准测试 406 | zig build bench 407 | 408 | # 使用 ReleaseFast 优化获取最佳性能数据 409 | zig build bench -Doptimize=ReleaseFast 410 | ``` 411 | 412 | 基准测试覆盖范围: 413 | - ✅ 基本类型(nil, bool, int, uint, float)的序列化/反序列化 414 | - ✅ 字符串和二进制数据(不同大小) 415 | - ✅ 数组和 Map(小型/中型/大型) 416 | - ✅ 扩展类型和 Timestamp 417 | - ✅ 嵌套结构和混合类型的实际场景 418 | 419 | 输出格式示例: 420 | ``` 421 | Benchmark Name | Iterations | ns/op | ops/sec 422 | ------------------------------------------------------------------------ 423 | Nil Write | 1000000 | 45 | 22222222 424 | Small Int Read | 1000000 | 123 | 8130081 425 | ``` 426 | 427 | --- 428 | 429 | ## 9. 开发最佳实践 430 | 431 | ### 9.1 添加新功能 432 | 433 | 1. **检查规范**:确认 MessagePack 规范要求 434 | 2. **更新类型**:修改 `Payload` 或添加新类型 435 | 3. **实现编码**:在 `Pack` 中添加 `writeXxx()` 方法 436 | 4. **实现解码**:在 `Pack` 中添加 `readXxx()` 方法 437 | 5. **添加测试**:在 `test.zig` 中覆盖所有情况 438 | 6. **版本兼容**:检查是否需要 `compat.zig` 支持 439 | 440 | ### 9.2 修复 Bug 441 | 442 | 1. **添加失败测试**:先写能重现问题的测试 443 | 2. **定位问题**:检查编码/解码/内存管理 444 | 3. **修复代码**:最小化改动范围 445 | 4. **验证测试**:确保新旧测试都通过 446 | 5. **检查内存**:运行 `zig build test` 确认无泄漏 447 | 448 | ### 9.3 代码审查检查项 449 | 450 | - [ ] 是否遵循最小编码原则? 451 | - [ ] 是否正确处理大端序? 452 | - [ ] 是否正确管理内存(free/errdefer)? 453 | - [ ] 是否添加了测试用例? 454 | - [ ] 是否兼容 Zig 0.14-0.15? 455 | - [ ] 是否更新了相关文档? 456 | - [ ] 是否使用了适当的 inline 提示? 457 | - [ ] 是否避免了代码重复? 458 | 459 | --- 460 | 461 | ## 10. 常见操作模式 462 | 463 | ### 10.1 创建复杂嵌套结构 464 | 465 | ```zig 466 | // 创建:{"name": "Alice", "scores": [95, 87, 92]} 467 | var root = Payload.mapPayload(allocator); 468 | defer root.free(allocator); 469 | 470 | try root.mapPut("name", try Payload.strToPayload("Alice", allocator)); 471 | 472 | var scores = try Payload.arrPayload(3, allocator); 473 | try scores.setArrElement(0, Payload.uintToPayload(95)); 474 | try scores.setArrElement(1, Payload.uintToPayload(87)); 475 | try scores.setArrElement(2, Payload.uintToPayload(92)); 476 | 477 | try root.mapPut("scores", scores); 478 | ``` 479 | 480 | ### 10.2 安全的类型转换 481 | 482 | ```zig 483 | // 获取可能是 int 或 uint 的值 484 | const value = try payload.getInt(); // 如果是 uint 且 <= i64::MAX,会自动转换 485 | 486 | // 获取 uint(拒绝负数) 487 | const positive_value = try payload.getUint(); // 负数 int 会返回 INVALID_TYPE 488 | ``` 489 | 490 | ### 10.3 遍历 Map 491 | 492 | ```zig 493 | var iterator = payload.map.iterator(); 494 | while (iterator.next()) |entry| { 495 | const key: []const u8 = entry.key_ptr.*; 496 | const value: Payload = entry.value_ptr.*; 497 | // 处理键值对 498 | } 499 | ``` 500 | 501 | --- 502 | 503 | ## 11. 性能考虑 504 | 505 | ### 11.1 序列化优化 506 | 507 | - 使用 `fixedBufferStream` 避免动态分配 508 | - 预分配足够大的缓冲区 509 | - 批量写入时重用 Pack 实例 510 | - 参考 `bench.zig` 中的性能基准数据优化热点路径 511 | 512 | ### 11.2 反序列化优化 513 | 514 | - 使用 Arena Allocator 批量释放 515 | - 避免不必要的深拷贝 516 | - 考虑使用 `std.testing.allocator` 检测泄漏 517 | 518 | --- 519 | 520 | ## 12. 调试技巧 521 | 522 | ### 12.1 查看序列化字节 523 | 524 | ```zig 525 | var arr: [1000]u8 = std.mem.zeroes([1000]u8); 526 | // ... 写入数据 ... 527 | std.debug.print("Bytes: {x}\n", .{arr[0..10]}); // 打印前 10 字节(十六进制) 528 | ``` 529 | 530 | ### 12.2 验证格式标记 531 | 532 | ```zig 533 | try expect(arr[0] == 0xc0); // 检查是否为 NIL 标记 534 | try expect(arr[0] == 0xd6); // 检查是否为 FIXEXT4 535 | ``` 536 | 537 | --- 538 | 539 | ## 13. LLM 协助规范 540 | 541 | ### 13.1 理解代码时 542 | 543 | 1. 先阅读本文档第 1-2 节,了解整体架构 544 | 2. 查看 `Payload` 定义,理解类型系统 545 | 3. 查看 `Pack` 泛型实现,理解序列化流程 546 | 4. 参考 `test.zig` 了解使用模式 547 | 548 | ### 13.2 回答问题时 549 | 550 | 1. 引用具体的类型/函数名称 551 | 2. 提供可运行的代码示例 552 | 3. 说明内存管理需求(是否需要 free) 553 | 4. 标注 Zig 版本兼容性(如果相关) 554 | 555 | ### 13.3 建议修改时 556 | 557 | 1. 提供完整的上下文(patch 格式) 558 | 2. 说明修改原因和影响范围 559 | 3. 包含测试用例建议 560 | 4. 提醒版本兼容性检查 561 | 562 | --- 563 | 564 | ## 14. 快速参考 565 | 566 | ### 14.1 文件定位 567 | 568 | | 需求 | 文件位置 | 569 | |------|---------| 570 | | 修改核心逻辑 | `src/msgpack.zig` | 571 | | 添加测试 | `src/test.zig` | 572 | | 添加/运行基准测试 | `src/bench.zig` | 573 | | 修复版本兼容 | `src/compat.zig` | 574 | | 更新构建配置 | `build.zig` | 575 | 576 | ### 14.2 常用常量 577 | 578 | ```zig 579 | MAX_POSITIVE_FIXINT: u8 = 0x7f // 127 580 | MIN_NEGATIVE_FIXINT: i8 = -32 581 | MAX_FIXSTR_LEN: u8 = 31 582 | MAX_FIXARRAY_LEN: u8 = 15 583 | MAX_FIXMAP_LEN: u8 = 15 584 | TIMESTAMP_EXT_TYPE: i8 = -1 585 | ``` 586 | 587 | ### 14.3 格式标记快查 588 | 589 | | 类型 | 标记值 | 说明 | 590 | |------|-------|------| 591 | | NIL | 0xc0 | 空值 | 592 | | TRUE | 0xc3 | 真 | 593 | | FALSE | 0xc2 | 假 | 594 | | UINT8 | 0xcc | 8 位无符号整数 | 595 | | INT8 | 0xd0 | 8 位有符号整数 | 596 | | FLOAT32 | 0xca | 32 位浮点 | 597 | | FLOAT64 | 0xcb | 64 位浮点 | 598 | | STR8 | 0xd9 | 8 位长度字符串 | 599 | | BIN8 | 0xc4 | 8 位长度二进制 | 600 | | ARRAY16 | 0xdc | 16 位长度数组 | 601 | | MAP16 | 0xde | 16 位长度 Map | 602 | | FIXEXT4 | 0xd6 | 4 字节扩展 | 603 | | FIXEXT8 | 0xd7 | 8 字节扩展 | 604 | | EXT8 | 0xc7 | 8 位长度扩展 | 605 | 606 | --- 607 | 608 | ## 15. 更新日志追踪 609 | 610 | 修改代码时,请在此记录重要变更: 611 | 612 | ### 格式 613 | ``` 614 | [日期] [修改类型] 简要描述 615 | - 详细说明 1 616 | - 详细说明 2 617 | ``` 618 | 619 | ### 示例 620 | ``` 621 | [2025-10-18] [Feature] 添加性能基准测试套件 622 | - 创建 src/bench.zig 完整基准测试文件 623 | - 覆盖所有主要类型的序列化/反序列化性能 624 | - 包含小/中/大规模容器的性能对比测试 625 | - 测试嵌套结构和混合类型的实际应用场景 626 | - 在 build.zig 中添加 `bench` 构建目标 627 | - 提供详细的吞吐量(ops/sec)和延迟(ns/op)指标输出 628 | 629 | [2025-10-03] [Feature] 添加 Timestamp 支持 630 | - 实现三种 timestamp 格式(32/64/96 位) 631 | - 添加 Timestamp.toFloat() 转换方法 632 | - 完整测试覆盖所有边界情况 633 | 634 | [2025-10-18] [Optimization] 高优先级性能优化 635 | - markerU8To 改用 switch 表达式(+10-20% 解析性能) 636 | - 整数读写泛型化(减少150行重复代码) 637 | - 添加 inline 提示到25+个热点函数(+5-15% 性能) 638 | 639 | [2025-10-18] [Refactor] 中优先级代码重构 640 | - 常量重组为语义化结构体(FixLimits, IntBounds 等) 641 | - 拆分 readExtValueOrTimestamp 为多个小函数 642 | - 统一数据写入逻辑(writeDataWithLength) 643 | - 添加严格类型转换 API(asInt, asUint, asFloat 等) 644 | - 添加类型检查方法(isNil, isNumber, isInteger) 645 | ``` 646 | 647 | --- 648 | 649 | ## 16. 附录 650 | 651 | ### 16.1 相关链接 652 | 653 | - MessagePack 官方规范: https://msgpack.org/ 654 | - Zig 语言文档: https://ziglang.org/documentation/master/ 655 | - 项目仓库: [填写实际 URL] 656 | 657 | ### 16.2 术语表 658 | 659 | | 术语 | 英文 | 说明 | 660 | |------|------|------| 661 | | 载荷 | Payload | 核心数据容器类型 | 662 | | 序列化 | Serialization | 将数据转为字节流 | 663 | | 反序列化 | Deserialization | 将字节流转为数据 | 664 | | 大端序 | Big Endian | 高位字节在前的存储方式 | 665 | | 固定格式 | Fix Format | 单字节编码的紧凑格式 | 666 | | 扩展类型 | Extension Type | 用户自定义类型(-128 到 127) | 667 | 668 | --- 669 | 670 | **文档版本**: 1.0 671 | **最后更新**: 2025-10-03 672 | **维护者**: AI Assistant 673 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # zig-msgpack 2 | 3 | [![CI](https://github.com/zigcc/zig-msgpack/actions/workflows/test.yml/badge.svg)](https://github.com/zigcc/zig-msgpack/actions/workflows/test.yml) 4 | 5 | Zig 编程语言的 MessagePack 实现。此库提供了一种简单高效的方式来使用 MessagePack 格式序列化和反序列化数据。 6 | 7 | 相关介绍文章: [Zig Msgpack](https://blog.nvimer.org/2025/09/20/zig-msgpack/) 8 | 9 | ## 特性 10 | 11 | - **完整的 MessagePack 支持**: 实现了所有 MessagePack 类型,包括时间戳扩展类型。 12 | - **时间戳支持**: 完整实现 MessagePack 时间戳扩展类型 (-1),支持所有三种格式(32位、64位和96位)。 13 | - **生产级安全解析器**: 迭代式解析器防止深度嵌套或恶意输入导致的栈溢出。 14 | - **安全加固**: 可配置的限制保护,防御 DoS 攻击(深度炸弹、大小炸弹等)。 15 | - **高效**: 设计追求高性能,内存开销最小。 16 | - **类型安全**: 利用 Zig 的类型系统确保序列化和反序列化期间的安全性。 17 | - **简单的 API**: 提供直观易用的编码和解码 API。 18 | - **泛型 Map 键**: 支持任意 Payload 类型作为 map 键,不仅限于字符串(使用高效的 HashMap 实现)。 19 | - **性能优化**: 高级优化包括 CPU 缓存预取、分支预测提示和 SIMD 操作,实现最大吞吐量。 20 | - **跨平台**: 在所有主流平台(Windows、macOS、Linux)和架构(x86_64、ARM64等)上测试和优化,具有平台特定的优化。 21 | 22 | ## 平台支持 23 | 24 | 本库在所有主流平台和架构上经过测试和优化: 25 | 26 | | 平台 | 架构 | CI 状态 | SIMD 优化 | 27 | |----------|--------------|-----------|-------------------| 28 | | **Windows** | x86_64 | ✅ 已测试 | SSE2/AVX2 预取 | 29 | | **macOS** | ARM64 (Apple Silicon) | ✅ 已测试 | ARM NEON + PRFM | 30 | | **Linux** | x86_64 | ✅ 已测试 | SSE2/AVX2 预取 | 31 | | **Linux** | ARM64/aarch64 | ✅ 已测试 | ARM NEON + PRFM | 32 | | **其他** | RISC-V、MIPS 等 | ✅ 已测试 | 优雅降级 | 33 | 34 | ### 架构特定优化 35 | 36 | - **x86/x64**: 利用 SSE/AVX 预取指令(`PREFETCHT0/1/2`、`PREFETCHNTA`)实现缓存感知的内存访问 37 | - **ARM64**: 使用 ARM PRFM(预取内存)指令在 Apple Silicon 和 ARM 服务器上实现最佳性能 38 | - **跨平台**: 编译时自动检测 CPU 特性,零运行时开销 39 | - **安全降级**: 在不支持的架构上优雅地降级为标准操作 40 | 41 | ## 安装 42 | 43 | ### 版本兼容性 44 | 45 | | Zig 版本 | 库版本 | 状态 | 46 | | -------------------- | -------- | ----------------- | 47 | | 0.13 及更早版本 | 0.0.6 | 旧版支持 | 48 | | 0.14.0 | 当前版本 | ✅ 完全支持 | 49 | | 0.15.x | 当前版本 | ✅ 完全支持 | 50 | | 0.16.0-dev (nightly) | 当前版本 | ✅ 通过兼容层支持 | 51 | 52 | > **注意**: 对于 Zig 0.13 及更早版本,请使用本库的 `0.0.6` 版本。 53 | > **注意**: Zig 0.16+ 移除了 `std.io.FixedBufferStream`,但本库提供了兼容层以在所有支持的版本中维持相同的 API。 54 | 55 | 对于 Zig `0.14.0`、`0.15.x` 和 `0.16.0-dev` 版本,请按以下步骤操作: 56 | 57 | 1. **添加为依赖项**: 58 | 将库添加到您的 `build.zig.zon` 文件中。您可以获取特定的提交或分支。 59 | 60 | ```sh 61 | zig fetch --save https://github.com/zigcc/zig-msgpack/archive/{COMMIT_OR_BRANCH}.tar.gz 62 | ``` 63 | 64 | 2. **配置您的 `build.zig`**: 65 | 将 `zig-msgpack` 模块添加到您的可执行文件中。 66 | 67 | ### 使用 std.io.Reader 和 std.io.Writer(Zig 0.15+) 68 | 69 | 对于 Zig 0.15 及更高版本,您可以使用便捷的 `PackerIO` API 配合标准 I/O 接口: 70 | 71 | ```zig 72 | const std = @import("std"); 73 | const msgpack = @import("msgpack"); 74 | 75 | pub fn main() !void { 76 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 77 | defer _ = gpa.deinit(); 78 | const allocator = gpa.allocator(); 79 | 80 | var buffer: [1024]u8 = undefined; 81 | 82 | // 创建 Reader 和 Writer 83 | var writer = std.Io.Writer.fixed(&buffer); 84 | var reader = std.Io.Reader.fixed(&buffer); 85 | 86 | // 使用便捷的 PackerIO 创建 packer 87 | var packer = msgpack.PackerIO.init(&reader, &writer); 88 | 89 | // 创建和编码数据 90 | var map = msgpack.Payload.mapPayload(allocator); 91 | defer map.free(allocator); 92 | try map.mapPut("姓名", try msgpack.Payload.strToPayload("小明", allocator)); 93 | try map.mapPut("年龄", msgpack.Payload.uintToPayload(25)); 94 | try packer.write(map); 95 | 96 | // 解码 97 | reader.seek = 0; 98 | const decoded = try packer.read(allocator); 99 | defer decoded.free(allocator); 100 | 101 | const name = (try decoded.mapGet("姓名")).?.str.value(); 102 | const age = (try decoded.mapGet("年龄")).?.uint; 103 | std.debug.print("姓名: {s}, 年龄: {d}\n", .{ name, age }); 104 | } 105 | ``` 106 | 107 | 您也可以使用便捷函数: 108 | 109 | ```zig 110 | var packer = msgpack.packIO(&reader, &writer); 111 | ``` 112 | 113 | ### 文件操作 114 | 115 | ```zig 116 | const std = @import("std"); 117 | const msgpack = @import("msgpack"); 118 | 119 | pub fn main() !void { 120 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 121 | defer _ = gpa.deinit(); 122 | const allocator = gpa.allocator(); 123 | 124 | // 打开文件进行读写 125 | var file = try std.fs.cwd().createFile("data.msgpack", .{ .read = true }); 126 | defer file.close(); 127 | 128 | // 创建带缓冲区的 reader 和 writer 129 | var reader_buf: [4096]u8 = undefined; 130 | var reader = file.reader(&reader_buf); 131 | var writer_buf: [4096]u8 = undefined; 132 | var writer = file.writer(&writer_buf); 133 | 134 | var packer = msgpack.PackerIO.init(&reader, &writer); 135 | 136 | // 序列化数据 137 | var payload = msgpack.Payload.mapPayload(allocator); 138 | defer payload.free(allocator); 139 | try payload.mapPut("消息", try msgpack.Payload.strToPayload("你好,MessagePack!", allocator)); 140 | try packer.write(payload); 141 | 142 | // 刷新并回到文件开始位置 143 | try writer.flush(); 144 | try file.seekTo(0); 145 | reader.seek = 0; 146 | reader.end = 0; 147 | 148 | // 反序列化 149 | const decoded = try packer.read(allocator); 150 | defer decoded.free(allocator); 151 | 152 | const message = (try decoded.mapGet("消息")).?.str.value(); 153 | std.debug.print("消息: {s}\n", .{message}); 154 | } 155 | ``` 156 | 157 | ### 基础用法(所有 Zig 版本) 158 | 159 | 为了最大兼容性或需要更多控制时,使用泛型 `Pack` API: 160 | 161 | ```zig 162 | const std = @import("std"); 163 | 164 | pub fn build(b: *std.Build) void { 165 | const target = b.standardTargetOptions(.{}); 166 | const optimize = b.standardOptimizeOption(.{}); 167 | 168 | const exe = b.addExecutable(.{ 169 | .name = "my-app", 170 | .root_source_file = .{ .path = "src/main.zig" }, 171 | .target = target, 172 | .optimize = optimize, 173 | }); 174 | 175 | const msgpack_dep = b.dependency("zig_msgpack", .{ 176 | .target = target, 177 | .optimize = optimize, 178 | }); 179 | 180 | exe.root_module.addImport("msgpack", msgpack_dep.module("msgpack")); 181 | 182 | b.installArtifact(exe); 183 | } 184 | ``` 185 | 186 | ## 使用方法 187 | 188 | ### 基础用法 189 | 190 | ```zig 191 | const std = @import("std"); 192 | const msgpack = @import("msgpack"); 193 | 194 | pub fn main() !void { 195 | const allocator = std.heap.page_allocator; 196 | var buffer: [1024]u8 = undefined; 197 | 198 | // 使用兼容层实现跨版本支持 199 | const compat = msgpack.compat; 200 | var write_buffer = compat.fixedBufferStream(&buffer); 201 | var read_buffer = compat.fixedBufferStream(&buffer); 202 | 203 | const BufferType = compat.BufferStream; 204 | var packer = msgpack.Pack( 205 | *BufferType, *BufferType, 206 | BufferType.WriteError, BufferType.ReadError, 207 | BufferType.write, BufferType.read, 208 | ).init(&write_buffer, &read_buffer); 209 | 210 | // 创建和编码数据 211 | var map = msgpack.Payload.mapPayload(allocator); 212 | defer map.free(allocator); 213 | try map.mapPut("姓名", try msgpack.Payload.strToPayload("小明", allocator)); 214 | try map.mapPut("年龄", msgpack.Payload.uintToPayload(25)); 215 | try packer.write(map); 216 | 217 | // 解码 218 | read_buffer.pos = 0; 219 | const decoded = try packer.read(allocator); 220 | defer decoded.free(allocator); 221 | 222 | const name = (try decoded.mapGet("姓名")).?.str.value(); 223 | const age = (try decoded.mapGet("年龄")).?.uint; 224 | std.debug.print("姓名: {s}, 年龄: {d}\n", .{ name, age }); 225 | } 226 | ``` 227 | 228 | ### 数据类型 229 | 230 | ```zig 231 | // 基础类型 232 | const nil_val = msgpack.Payload.nilToPayload(); 233 | const bool_val = msgpack.Payload.boolToPayload(true); 234 | const int_val = msgpack.Payload.intToPayload(-42); 235 | const uint_val = msgpack.Payload.uintToPayload(42); 236 | const float_val = msgpack.Payload.floatToPayload(3.14); 237 | 238 | // 字符串和二进制数据 239 | const str_val = try msgpack.Payload.strToPayload("你好", allocator); 240 | const bin_val = try msgpack.Payload.binToPayload(&[_]u8{1, 2, 3}, allocator); 241 | 242 | // 数组 243 | var arr = try msgpack.Payload.arrPayload(2, allocator); 244 | try arr.setArrElement(0, msgpack.Payload.intToPayload(1)); 245 | try arr.setArrElement(1, msgpack.Payload.intToPayload(2)); 246 | 247 | // 扩展类型 248 | const ext_val = try msgpack.Payload.extToPayload(5, &[_]u8{0xaa, 0xbb}, allocator); 249 | 250 | // 使用字符串键的 Map(向后兼容) 251 | var map = msgpack.Payload.mapPayload(allocator); 252 | try map.mapPut("键1", msgpack.Payload.intToPayload(42)); 253 | 254 | // 使用任意类型键的 Map(新特性) 255 | var generic_map = msgpack.Payload.mapPayload(allocator); 256 | try generic_map.mapPutGeneric(msgpack.Payload.intToPayload(1), msgpack.Payload.strToPayload("值1", allocator)); 257 | try generic_map.mapPutGeneric(msgpack.Payload.boolToPayload(true), msgpack.Payload.strToPayload("真值", allocator)); 258 | ``` 259 | 260 | ### 使用组织化的常量 261 | 262 | 库提供了语义化的常量结构,提高代码清晰度: 263 | 264 | ```zig 265 | // MessagePack 格式限制 266 | const max_fixstr = msgpack.FixLimits.STR_LEN_MAX; // 31 267 | const max_fixarray = msgpack.FixLimits.ARRAY_LEN_MAX; // 15 268 | 269 | // 整数边界 270 | const uint8_max = msgpack.IntBounds.UINT8_MAX; // 0xff 271 | const int8_min = msgpack.IntBounds.INT8_MIN; // -128 272 | 273 | // 固定扩展类型长度 274 | const ext4_len = msgpack.FixExtLen.EXT4; // 4 275 | 276 | // 时间戳常量 277 | const ts_type = msgpack.TimestampExt.TYPE_ID; // -1 278 | const ts32_len = msgpack.TimestampExt.FORMAT32_LEN; // 4 279 | const max_nano = msgpack.TimestampExt.NANOSECONDS_MAX; // 999_999_999 280 | 281 | // 标记基础值 282 | const fixarray_base = msgpack.MarkerBase.FIXARRAY; // 0x90 283 | const fixstr_mask = msgpack.MarkerBase.FIXSTR_LEN_MASK; // 0x1f 284 | ``` 285 | 286 | ### 时间戳用法 287 | 288 | ```zig 289 | // 创建时间戳 290 | const ts1 = msgpack.Payload.timestampFromSeconds(1234567890); 291 | const ts2 = msgpack.Payload.timestampToPayload(1234567890, 123456789); 292 | 293 | // 写入和读取时间戳 294 | try packer.write(ts2); 295 | read_buffer.pos = 0; 296 | const decoded_ts = try packer.read(allocator); 297 | defer decoded_ts.free(allocator); 298 | 299 | std.debug.print("时间戳: {}秒 + {}纳秒\n", 300 | .{ decoded_ts.timestamp.seconds, decoded_ts.timestamp.nanoseconds }); 301 | std.debug.print("浮点数形式: {d}\n", .{ decoded_ts.timestamp.toFloat() }); 302 | ``` 303 | 304 | ### 错误处理 305 | 306 | ```zig 307 | // 类型转换与错误处理 308 | const int_payload = msgpack.Payload.intToPayload(-42); 309 | const uint_result = int_payload.getUint() catch |err| switch (err) { 310 | msgpack.MsgPackError.InvalidType => { 311 | std.debug.print("无法将负数转换为无符号整数\n", .{}); 312 | return; 313 | }, 314 | else => return err, 315 | }; 316 | 317 | // 严格类型转换(无自动转换) 318 | const strict_int = payload.asInt() catch |err| { 319 | // 只接受 .int 类型,即使 .uint 值适合也会拒绝 320 | return err; 321 | }; 322 | 323 | // 类型检查 324 | if (payload.isNil()) { 325 | std.debug.print("值是 nil\n", .{}); 326 | } 327 | if (payload.isNumber()) { 328 | std.debug.print("值是数字(int/uint/float)\n", .{}); 329 | } 330 | if (payload.isInteger()) { 331 | std.debug.print("值是整数(int/uint)\n", .{}); 332 | } 333 | ``` 334 | 335 | ### 安全特性(解析不可信数据) 336 | 337 | 本库包含可配置的安全限制,用于防护恶意或损坏的 MessagePack 数据: 338 | 339 | ```zig 340 | // 默认限制(推荐用于大多数场景) 341 | const Packer = msgpack.Pack( 342 | *Writer, *Reader, 343 | Writer.Error, Reader.Error, 344 | Writer.write, Reader.read, 345 | ); 346 | // 自动防护: 347 | // - 深度嵌套攻击(最大 1000 层) 348 | // - 大数组/Map 攻击(最大 100 万元素) 349 | // - 内存耗尽(最大 100MB 字符串) 350 | 351 | // 针对特定环境的自定义限制 352 | const StrictPacker = msgpack.PackWithLimits( 353 | *Writer, *Reader, 354 | Writer.Error, Reader.Error, 355 | Writer.write, Reader.read, 356 | .{ 357 | .max_depth = 50, // 限制嵌套到 50 层 358 | .max_array_length = 10_000, // 最大 1 万个数组元素 359 | .max_map_size = 10_000, // 最大 1 万个 map 键值对 360 | .max_string_length = 1024 * 1024, // 最大 1MB 字符串 361 | .max_bin_length = 1024 * 1024, // 最大 1MB 二进制数据 362 | .max_ext_length = 512 * 1024, // 最大 512KB 扩展类型数据 363 | }, 364 | ); 365 | ``` 366 | 367 | **安全保证**: 368 | 369 | - ✅ **永不崩溃** - 任何畸形或恶意输入都不会导致崩溃 370 | - ✅ **无栈溢出** - 无论嵌套深度如何(迭代式解析器) 371 | - ✅ **内存可控** - 通过可配置限制控制内存使用 372 | - ✅ **快速拒绝** - 无效数据被立即拒绝(无资源耗尽) 373 | 374 | 可能的安全错误: 375 | 376 | ```zig 377 | msgpack.MsgPackError.MaxDepthExceeded // 嵌套过深 378 | msgpack.MsgPackError.ArrayTooLarge // 数组声称过多元素 379 | msgpack.MsgPackError.MapTooLarge // Map 声称过多键值对 380 | msgpack.MsgPackError.StringTooLong // 字符串过长 381 | msgpack.MsgPackError.BinDataLengthTooLong // 二进制数据过大 382 | msgpack.MsgPackError.ExtDataTooLarge // 扩展类型数据过大 383 | ``` 384 | 385 | ## API 概览 386 | 387 | - **`msgpack.Pack`**: 用于打包和解包 MessagePack 数据的主要结构体,带默认安全限制。 388 | - **`msgpack.PackWithLimits`**: 创建带自定义安全限制的 packer,满足特定安全需求。 389 | - **`msgpack.Payload`**: 表示任何 MessagePack 类型的联合体。提供创建和与不同数据类型交互的方法(例如 `mapPayload`、`strToPayload`、`mapGet`)。 390 | - **`msgpack.PackerIO`**:(Zig 0.15+)用于处理 `std.io.Reader` 和 `std.io.Writer` 的便捷包装器。 391 | - **`msgpack.packIO`**:(Zig 0.15+)创建 `PackerIO` 实例的便捷函数。 392 | - **`msgpack.ParseLimits`**: 解析器安全限制的配置结构体。 393 | - **常量结构体**: `FixLimits`、`IntBounds`、`FixExtLen`、`TimestampExt`、`MarkerBase` - 组织化的常量,提高代码清晰度。 394 | 395 | ### 类型转换方法 396 | 397 | **宽松转换**(允许类型转换): 398 | - `getInt()` - uint 可以转换为 i64(如果在范围内) 399 | - `getUint()` - 正数 int 可以转换为 u64 400 | 401 | **严格转换**(不允许类型转换): 402 | - `asInt()`、`asUint()`、`asFloat()`、`asBool()`、`asStr()`、`asBin()` 403 | 404 | **类型检查**: 405 | - `isNil()`、`isNumber()`、`isInteger()` 406 | 407 | ### Map 操作 408 | 409 | **字符串键**(向后兼容): 410 | - `mapPut(key: []const u8, value: Payload)` 411 | - `mapGet(key: []const u8) ?Payload` 412 | 413 | **泛型键**(任意 Payload 类型): 414 | - `mapPutGeneric(key: Payload, value: Payload)` 415 | - `mapGetGeneric(key: Payload) ?Payload` 416 | 417 | ## 实现说明 418 | 419 | ### 安全架构 420 | 421 | 本库使用**迭代式解析器**(非递归)提供强大的安全保证: 422 | 423 | **迭代式解析**: 424 | 425 | - 解析器使用显式栈(堆上)而非递归函数调用 426 | - 栈深度恒定,与输入嵌套深度无关 427 | - 完全防止栈溢出攻击 428 | 429 | **安全限制**: 430 | 431 | - 所有限制在内存分配**之前**强制执行 432 | - 无效输入被立即拒绝,不消耗资源 433 | - 可配置限制允许针对特定环境调整(嵌入式、服务器等) 434 | 435 | **内存安全**: 436 | 437 | - 所有错误路径包含完整清理(`errdefer` + `cleanupParseStack`) 438 | - 零内存泄漏(测试中由 GPA 验证) 439 | - 可安全解析来自网络、文件或用户输入的不可信数据 440 | 441 | ### Zig 0.16 兼容性 442 | 443 | 从 Zig 0.16 开始,标准库的 I/O 子系统经历了重大变更。作为更广泛重新设计的一部分,`std.io.FixedBufferStream` 被移除。本库包含一个兼容层(`src/compat.zig`),它: 444 | 445 | - 为 Zig 0.16+ 提供了一个 `BufferStream` 实现,模拟旧版 `FixedBufferStream` 的行为 446 | - 使用条件编译来保持与 Zig 0.14 和 0.15 的向后兼容性 447 | - 确保所有现有功能在不同 Zig 版本间无缝工作 448 | 449 | 这意味着无论您使用哪个 Zig 版本,都可以使用相同的 API,库会在内部处理差异。 450 | 451 | ## 测试 452 | 453 | 要运行此库的单元测试,请使用以下命令: 454 | 455 | ```sh 456 | zig build test 457 | 458 | # 获取更详细的测试输出 459 | zig build test --summary all 460 | ``` 461 | 462 | 综合测试套件包括: 463 | 464 | - **87 个测试** 覆盖所有功能 465 | - **恶意数据测试**:验证针对精心构造的攻击(数十亿元素数组、极端嵌套等)的防护 466 | - **模糊测试**:随机输入验证,确保任意数据都不会崩溃 467 | - **大数据测试**:1000+ 元素的数组、500+ 键值对的 map 468 | - **内存安全**:严格分配器测试验证零泄漏 469 | 470 | ## 性能基准测试 471 | 472 | 运行性能基准测试: 473 | 474 | ```sh 475 | # 运行基准测试(默认构建模式) 476 | zig build bench 477 | 478 | # 使用优化模式运行以获得准确的性能测量结果 479 | zig build bench -Doptimize=ReleaseFast 480 | ``` 481 | 482 | 基准测试套件包括: 483 | 484 | - 基本类型(nil、bool、整数、浮点数) 485 | - 不同大小的字符串和二进制数据 486 | - 数组和映射表(小型、中型、大型) 487 | - 扩展类型和时间戳 488 | - 嵌套结构和混合类型载荷 489 | 490 | 输出提供每个操作的吞吐量(ops/sec)和延迟(ns/op)指标。 491 | 492 | ## 性能 493 | 494 | 本库在所有支持的平台上都经过深度性能优化。优化是架构感知的,在编译时自动适应您的目标平台。 495 | 496 | ### 优化特性 497 | 498 | 1. **CPU 缓存预取** 499 | - 平台特定的预取指令(x86: `PREFETCH*`,ARM: `PRFM`) 500 | - 智能预取 ≥256 字节的容器数据 501 | - 多级缓存提示(L1/L2/L3)实现最佳缓存利用 502 | - 非时序数据访问的流式预取 503 | 504 | 2. **SIMD 操作** 505 | - 自动检测可用的 SIMD 特性(AVX-512、AVX2、SSE2、NEON) 506 | - 向量化字符串比较(16-64 字节块) 507 | - 向量化内存拷贝与对齐优化 508 | - 向量化字节序转换(大端 ↔ 小端) 509 | - 批量整数数组转换(u32/u64) 510 | 511 | 3. **内存对齐优化** 512 | - 自动对齐检测和快速路径选择 513 | - 支持类型的对齐内存读写 514 | - 更好 SIMD 性能的内存对齐预处理 515 | - 大数据拷贝优化(≥64 字节) 516 | 517 | 4. **分支预测优化** 518 | - 常见情况的热路径注解 519 | - O(1) 标记字节转换的查找表(256 项预计算表) 520 | - 使用 switch 表达式而非 if-else 链(跳转表优化) 521 | - 优化的错误处理路径 522 | 523 | 5. **基于 HashMap 的 Map** 524 | - O(1) 平均情况的键查找(vs O(n) 线性搜索) 525 | - 高效的哈希函数与深度限制 526 | - 支持任意 Payload 类型作为键 527 | - `getOrPut` 优化(单次哈希计算) 528 | 529 | ### 性能特征 530 | 531 | 相对于基线实现的测量性能提升: 532 | 533 | | 操作类型 | 性能提升 | 吞吐量 | 关键优化 | 534 | | ------------------------- | ----------- | ------------------------ | ----------------------------------------- | 535 | | 小型/简单数据 | 5-10% | ~2000万 次操作/秒 | 分支预测、查找表 | 536 | | 大字符串(≥256B) | 15-25% | ~2-5 GB/秒 | 预取、SIMD 比较 | 537 | | 大二进制(≥256B) | 15-25% | ~3-6 GB/秒 | 预取、SIMD memcpy | 538 | | 整数数组(100+) | 10-20% | ~50-100万 数组/秒 | 批量转换、预取 | 539 | | Map 查找(100+ 键) | 50-90% | ~500-1000万 查找/秒 | HashMap O(1) vs 线性 O(n) | 540 | | 嵌套结构 | 8-15% | ~10-50万 结构/秒 | 组合优化 | 541 | | 混合类型数据 | 10-15% | 根据数据变化 | 自适应优化 | 542 | 543 | > **注意**: 性能因平台、CPU 型号、数据大小和编译器优化级别(`ReleaseFast` vs `ReleaseSafe`)而异。 544 | > 测量数据基于现代 CPU(Intel Core i7/i9、Apple M1/M2、AMD Ryzen)。 545 | 546 | ### 平台特定优化 547 | 548 | | 平台 | SIMD 特性 | 预取指令 | 字符串比较 | 内存拷贝 | 549 | |----------|---------------|----------------------|-------------------|-------------| 550 | | **x86_64 (AVX-512)** | 512 位向量 | `PREFETCHT0/1/2/NTA` | 64 字节块 | 64 字节块 | 551 | | **x86_64 (AVX2)** | 256 位向量 | `PREFETCHT0/1/2/NTA` | 32 字节块 | 32 字节块 | 552 | | **x86_64 (SSE2)** | 128 位向量 | `PREFETCHT0/1/2/NTA` | 16 字节块 | 16 字节块 | 553 | | **ARM64 (NEON)** | 128 位向量 | `PRFM PLD/PST` | 16 字节块 | 16 字节块 | 554 | | **其他** | 标量回退 | 无预取 | 标准 `memcmp` | 标准 `memcpy` | 555 | 556 | 所有优化都是**编译时检测**的,零运行时开销。库自动使用目标平台的最佳可用特性。 557 | 558 | ### 运行性能测试 559 | 560 | ```sh 561 | # 标准基准测试套件 562 | zig build bench -Doptimize=ReleaseFast 563 | 564 | # 示例输出: 565 | # 基准测试名称 | 迭代次数 | ns/操作 | 操作/秒 566 | # ------------------------------------------------------------------------ 567 | # Nil 写入 | 1000000 | 45 | 22222222 568 | # 小整数写入 | 1000000 | 52 | 19230769 569 | # 大字符串写入(1KB) | 100000 | 1250 | 800000 570 | # Map 查找(100 键) | 500000 | 180 | 5555555 571 | ``` 572 | 573 | ## 文档 574 | 575 | 要生成此库的文档: 576 | 577 | ```sh 578 | zig build docs 579 | ``` 580 | 581 | ## 贡献 582 | 583 | 欢迎贡献!请随时提出问题或提交拉取请求。 584 | 585 | ## 相关项目 586 | 587 | - [getty-msgpack](https://git.mzte.de/LordMZTE/getty-msgpack) 588 | - [znvim](https://github.com/jinzhongjia/znvim) 589 | 590 | ## 许可证 591 | 592 | 此项目在 MIT 许可证下许可。详情请参阅 [LICENSE](LICENSE) 文件。 593 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-msgpack 2 | 3 | [![CI](https://github.com/zigcc/zig-msgpack/actions/workflows/test.yml/badge.svg)](https://github.com/zigcc/zig-msgpack/actions/workflows/test.yml) 4 | 5 | A MessagePack implementation for the Zig programming language. This library provides a simple and efficient way to serialize and deserialize data using the MessagePack format. 6 | 7 | An article introducing it: [Zig Msgpack](https://blog.nvimer.org/2025/09/20/zig-msgpack/) 8 | 9 | ## Features 10 | 11 | - **Full MessagePack Support:** Implements all MessagePack types including the timestamp extension. 12 | - **Timestamp Support:** Complete implementation of MessagePack timestamp extension type (-1) with support for all three formats (32-bit, 64-bit, and 96-bit). 13 | - **Production-Safe Parser:** Iterative parser prevents stack overflow on deeply nested or malicious input. 14 | - **Security Hardened:** Configurable limits protect against DoS attacks (depth bombs, size bombs, etc.). 15 | - **Efficient:** Designed for high performance with minimal memory overhead. 16 | - **Type-Safe:** Leverages Zig's type system to ensure safety during serialization and deserialization. 17 | - **Simple API:** Offers a straightforward and easy-to-use API for encoding and decoding. 18 | - **Generic Map Keys:** Supports any Payload type as map keys, not limited to strings (uses efficient HashMap implementation). 19 | - **Performance Optimized:** Advanced optimizations including CPU cache prefetching, branch prediction hints, and SIMD operations for maximum throughput. 20 | - **Cross-Platform:** Tested and optimized for all major platforms (Windows, macOS, Linux) and architectures (x86_64, ARM64, etc.) with platform-specific optimizations. 21 | 22 | ## Platform Support 23 | 24 | This library is tested and optimized for all major platforms and architectures: 25 | 26 | | Platform | Architecture | CI Status | SIMD Optimizations | 27 | |----------|--------------|-----------|-------------------| 28 | | **Windows** | x86_64 | ✅ Tested | SSE2/AVX2 prefetch | 29 | | **macOS** | ARM64 (Apple Silicon) | ✅ Tested | ARM NEON + PRFM | 30 | | **Linux** | x86_64 | ✅ Tested | SSE2/AVX2 prefetch | 31 | | **Linux** | ARM64/aarch64 | ✅ Tested | ARM NEON + PRFM | 32 | | **Other** | RISC-V, MIPS, etc. | ✅ Tested | Graceful fallback | 33 | 34 | ### Architecture-Specific Optimizations 35 | 36 | - **x86/x64**: Utilizes SSE/AVX prefetch instructions (`PREFETCHT0/1/2`, `PREFETCHNTA`) for cache-aware memory access 37 | - **ARM64**: Uses ARM PRFM (Prefetch Memory) instructions for optimal performance on Apple Silicon and ARM servers 38 | - **Cross-platform**: Automatically detects CPU features at compile-time with zero runtime overhead 39 | - **Safe fallback**: Gracefully degrades to standard operations on unsupported architectures 40 | 41 | ## Installation 42 | 43 | ### Version Compatibility 44 | 45 | | Zig Version | Library Version | Status | 46 | | -------------------- | --------------- | ------------------------------------- | 47 | | 0.13 and older | 0.0.6 | Legacy support | 48 | | 0.14.0 | Current | ✅ Fully supported | 49 | | 0.15.x | Current | ✅ Fully supported | 50 | | 0.16.0-dev (nightly) | Current | ✅ Supported with compatibility layer | 51 | 52 | > **Note:** For Zig 0.13 and older versions, please use version `0.0.6` of this library. 53 | > **Note:** Zig 0.16+ removes `std.io.FixedBufferStream`, but this library provides a compatibility layer to maintain the same API across all supported versions. 54 | 55 | For Zig `0.14.0`, `0.15.x`, and `0.16.0-dev`, follow these steps: 56 | 57 | 1. **Add as a dependency:** 58 | Add the library to your `build.zig.zon` file. You can fetch a specific commit or branch. 59 | 60 | ```sh 61 | zig fetch --save https://github.com/zigcc/zig-msgpack/archive/{COMMIT_OR_BRANCH}.tar.gz 62 | ``` 63 | 64 | 2. **Configure your `build.zig`:** 65 | Add the `zig-msgpack` module to your executable. 66 | 67 | ### Using std.io.Reader and std.io.Writer (Zig 0.15+) 68 | 69 | For Zig 0.15 and later, you can use the convenient `PackerIO` API with standard I/O interfaces: 70 | 71 | ```zig 72 | const std = @import("std"); 73 | const msgpack = @import("msgpack"); 74 | 75 | pub fn main() !void { 76 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 77 | defer _ = gpa.deinit(); 78 | const allocator = gpa.allocator(); 79 | 80 | var buffer: [1024]u8 = undefined; 81 | 82 | // Create Reader and Writer 83 | var writer = std.Io.Writer.fixed(&buffer); 84 | var reader = std.Io.Reader.fixed(&buffer); 85 | 86 | // Create packer using the convenient PackerIO 87 | var packer = msgpack.PackerIO.init(&reader, &writer); 88 | 89 | // Create and encode data 90 | var map = msgpack.Payload.mapPayload(allocator); 91 | defer map.free(allocator); 92 | try map.mapPut("name", try msgpack.Payload.strToPayload("Alice", allocator)); 93 | try map.mapPut("age", msgpack.Payload.uintToPayload(30)); 94 | try packer.write(map); 95 | 96 | // Decode 97 | reader.seek = 0; 98 | const decoded = try packer.read(allocator); 99 | defer decoded.free(allocator); 100 | 101 | const name = (try decoded.mapGet("name")).?.str.value(); 102 | const age = (try decoded.mapGet("age")).?.uint; 103 | std.debug.print("Name: {s}, Age: {d}\n", .{ name, age }); 104 | } 105 | ``` 106 | 107 | You can also use the convenience function: 108 | 109 | ```zig 110 | var packer = msgpack.packIO(&reader, &writer); 111 | ``` 112 | 113 | ### Working with Files 114 | 115 | ```zig 116 | const std = @import("std"); 117 | const msgpack = @import("msgpack"); 118 | 119 | pub fn main() !void { 120 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 121 | defer _ = gpa.deinit(); 122 | const allocator = gpa.allocator(); 123 | 124 | // Open file for reading and writing 125 | var file = try std.fs.cwd().createFile("data.msgpack", .{ .read = true }); 126 | defer file.close(); 127 | 128 | // Create reader and writer with buffers 129 | var reader_buf: [4096]u8 = undefined; 130 | var reader = file.reader(&reader_buf); 131 | var writer_buf: [4096]u8 = undefined; 132 | var writer = file.writer(&writer_buf); 133 | 134 | var packer = msgpack.PackerIO.init(&reader, &writer); 135 | 136 | // Serialize data 137 | var payload = msgpack.Payload.mapPayload(allocator); 138 | defer payload.free(allocator); 139 | try payload.mapPut("message", try msgpack.Payload.strToPayload("Hello, MessagePack!", allocator)); 140 | try packer.write(payload); 141 | 142 | // Flush and seek back to start 143 | try writer.flush(); 144 | try file.seekTo(0); 145 | reader.seek = 0; 146 | reader.end = 0; 147 | 148 | // Deserialize 149 | const decoded = try packer.read(allocator); 150 | defer decoded.free(allocator); 151 | 152 | const message = (try decoded.mapGet("message")).?.str.value(); 153 | std.debug.print("Message: {s}\n", .{message}); 154 | } 155 | ``` 156 | 157 | ### Basic Usage (All Zig Versions) 158 | 159 | For maximum compatibility or when you need more control, use the generic `Pack` API: 160 | 161 | ```zig 162 | const std = @import("std"); 163 | 164 | pub fn build(b: *std.Build) void { 165 | const target = b.standardTargetOptions(.{}); 166 | const optimize = b.standardOptimizeOption(.{}); 167 | 168 | const exe = b.addExecutable(.{ 169 | .name = "my-app", 170 | .root_source_file = .{ .path = "src/main.zig" }, 171 | .target = target, 172 | .optimize = optimize, 173 | }); 174 | 175 | const msgpack_dep = b.dependency("zig_msgpack", .{ 176 | .target = target, 177 | .optimize = optimize, 178 | }); 179 | 180 | exe.root_module.addImport("msgpack", msgpack_dep.module("msgpack")); 181 | 182 | b.installArtifact(exe); 183 | } 184 | ``` 185 | 186 | ## Usage 187 | 188 | ### Using std.io.Reader and std.io.Writer (Zig 0.15+) 189 | 190 | For Zig 0.15 and later, you can use the convenient `PackerIO` API with standard I/O interfaces: 191 | 192 | ```zig 193 | const std = @import("std"); 194 | const msgpack = @import("msgpack"); 195 | 196 | pub fn main() !void { 197 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 198 | defer _ = gpa.deinit(); 199 | const allocator = gpa.allocator(); 200 | 201 | var buffer: [1024]u8 = undefined; 202 | 203 | // Create Reader and Writer 204 | var writer = std.Io.Writer.fixed(&buffer); 205 | var reader = std.Io.Reader.fixed(&buffer); 206 | 207 | // Create packer using the convenient PackerIO 208 | var packer = msgpack.PackerIO.init(&reader, &writer); 209 | 210 | // Create and encode data 211 | var map = msgpack.Payload.mapPayload(allocator); 212 | defer map.free(allocator); 213 | try map.mapPut("name", try msgpack.Payload.strToPayload("Alice", allocator)); 214 | try map.mapPut("age", msgpack.Payload.uintToPayload(30)); 215 | try packer.write(map); 216 | 217 | // Decode 218 | reader.seek = 0; 219 | const decoded = try packer.read(allocator); 220 | defer decoded.free(allocator); 221 | 222 | const name = (try decoded.mapGet("name")).?.str.value(); 223 | const age = (try decoded.mapGet("age")).?.uint; 224 | std.debug.print("Name: {s}, Age: {d}\n", .{ name, age }); 225 | } 226 | ``` 227 | 228 | You can also use the convenience function: 229 | 230 | ```zig 231 | var packer = msgpack.packIO(&reader, &writer); 232 | ``` 233 | 234 | ### Working with Files 235 | 236 | ```zig 237 | const std = @import("std"); 238 | const msgpack = @import("msgpack"); 239 | 240 | pub fn main() !void { 241 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 242 | defer _ = gpa.deinit(); 243 | const allocator = gpa.allocator(); 244 | 245 | // Open file for reading and writing 246 | var file = try std.fs.cwd().createFile("data.msgpack", .{ .read = true }); 247 | defer file.close(); 248 | 249 | // Create reader and writer with buffers 250 | var reader_buf: [4096]u8 = undefined; 251 | var reader = file.reader(&reader_buf); 252 | var writer_buf: [4096]u8 = undefined; 253 | var writer = file.writer(&writer_buf); 254 | 255 | var packer = msgpack.PackerIO.init(&reader, &writer); 256 | 257 | // Serialize data 258 | var payload = msgpack.Payload.mapPayload(allocator); 259 | defer payload.free(allocator); 260 | try payload.mapPut("message", try msgpack.Payload.strToPayload("Hello, MessagePack!", allocator)); 261 | try packer.write(payload); 262 | 263 | // Flush and seek back to start 264 | try writer.flush(); 265 | try file.seekTo(0); 266 | reader.seek = 0; 267 | reader.end = 0; 268 | 269 | // Deserialize 270 | const decoded = try packer.read(allocator); 271 | defer decoded.free(allocator); 272 | 273 | const message = (try decoded.mapGet("message")).?.str.value(); 274 | std.debug.print("Message: {s}\n", .{message}); 275 | } 276 | ``` 277 | 278 | ### Basic Usage (All Zig Versions) 279 | 280 | For maximum compatibility or when you need more control, use the generic `Pack` API: 281 | 282 | ```zig 283 | const std = @import("std"); 284 | const msgpack = @import("msgpack"); 285 | 286 | pub fn main() !void { 287 | const allocator = std.heap.page_allocator; 288 | var buffer: [1024]u8 = undefined; 289 | 290 | // Use the compatibility layer for cross-version support 291 | const compat = msgpack.compat; 292 | var write_buffer = compat.fixedBufferStream(&buffer); 293 | var read_buffer = compat.fixedBufferStream(&buffer); 294 | 295 | const BufferType = compat.BufferStream; 296 | var packer = msgpack.Pack( 297 | *BufferType, *BufferType, 298 | BufferType.WriteError, BufferType.ReadError, 299 | BufferType.write, BufferType.read, 300 | ).init(&write_buffer, &read_buffer); 301 | 302 | // Create and encode data 303 | var map = msgpack.Payload.mapPayload(allocator); 304 | defer map.free(allocator); 305 | try map.mapPut("name", try msgpack.Payload.strToPayload("Alice", allocator)); 306 | try map.mapPut("age", msgpack.Payload.uintToPayload(30)); 307 | try packer.write(map); 308 | 309 | // Decode 310 | read_buffer.pos = 0; 311 | const decoded = try packer.read(allocator); 312 | defer decoded.free(allocator); 313 | 314 | const name = (try decoded.mapGet("name")).?.str.value(); 315 | const age = (try decoded.mapGet("age")).?.uint; 316 | std.debug.print("Name: {s}, Age: {d}\n", .{ name, age }); 317 | } 318 | ``` 319 | 320 | ### Data Types 321 | 322 | ```zig 323 | // Basic types 324 | const nil_val = msgpack.Payload.nilToPayload(); 325 | const bool_val = msgpack.Payload.boolToPayload(true); 326 | const int_val = msgpack.Payload.intToPayload(-42); 327 | const uint_val = msgpack.Payload.uintToPayload(42); 328 | const float_val = msgpack.Payload.floatToPayload(3.14); 329 | 330 | // String and binary 331 | const str_val = try msgpack.Payload.strToPayload("hello", allocator); 332 | const bin_val = try msgpack.Payload.binToPayload(&[_]u8{1, 2, 3}, allocator); 333 | 334 | // Array 335 | var arr = try msgpack.Payload.arrPayload(2, allocator); 336 | try arr.setArrElement(0, msgpack.Payload.intToPayload(1)); 337 | try arr.setArrElement(1, msgpack.Payload.intToPayload(2)); 338 | 339 | // Extension type 340 | const ext_val = try msgpack.Payload.extToPayload(5, &[_]u8{0xaa, 0xbb}, allocator); 341 | 342 | // Map with string keys (backward compatible) 343 | var map = msgpack.Payload.mapPayload(allocator); 344 | try map.mapPut("key1", msgpack.Payload.intToPayload(42)); 345 | 346 | // Map with any type keys (new feature) 347 | var generic_map = msgpack.Payload.mapPayload(allocator); 348 | try generic_map.mapPutGeneric(msgpack.Payload.intToPayload(1), msgpack.Payload.strToPayload("value1", allocator)); 349 | try generic_map.mapPutGeneric(msgpack.Payload.boolToPayload(true), msgpack.Payload.strToPayload("true_value", allocator)); 350 | ``` 351 | 352 | ### Using Organized Constants 353 | 354 | The library provides semantic constant structures for better code clarity: 355 | 356 | ```zig 357 | // MessagePack format limits 358 | const max_fixstr = msgpack.FixLimits.STR_LEN_MAX; // 31 359 | const max_fixarray = msgpack.FixLimits.ARRAY_LEN_MAX; // 15 360 | 361 | // Integer bounds 362 | const uint8_max = msgpack.IntBounds.UINT8_MAX; // 0xff 363 | const int8_min = msgpack.IntBounds.INT8_MIN; // -128 364 | 365 | // Fixed extension lengths 366 | const ext4_len = msgpack.FixExtLen.EXT4; // 4 367 | 368 | // Timestamp constants 369 | const ts_type = msgpack.TimestampExt.TYPE_ID; // -1 370 | const ts32_len = msgpack.TimestampExt.FORMAT32_LEN; // 4 371 | const max_nano = msgpack.TimestampExt.NANOSECONDS_MAX; // 999_999_999 372 | 373 | // Marker base values 374 | const fixarray_base = msgpack.MarkerBase.FIXARRAY; // 0x90 375 | const fixstr_mask = msgpack.MarkerBase.FIXSTR_LEN_MASK; // 0x1f 376 | ``` 377 | 378 | ### Timestamp Usage 379 | 380 | ```zig 381 | // Create timestamps 382 | const ts1 = msgpack.Payload.timestampFromSeconds(1234567890); 383 | const ts2 = msgpack.Payload.timestampToPayload(1234567890, 123456789); 384 | 385 | // Write and read timestamp 386 | try packer.write(ts2); 387 | read_buffer.pos = 0; 388 | const decoded_ts = try packer.read(allocator); 389 | defer decoded_ts.free(allocator); 390 | 391 | std.debug.print("Timestamp: {}s + {}ns\n", 392 | .{ decoded_ts.timestamp.seconds, decoded_ts.timestamp.nanoseconds }); 393 | std.debug.print("As float: {d}\n", .{ decoded_ts.timestamp.toFloat() }); 394 | ``` 395 | 396 | ### Error Handling 397 | 398 | ```zig 399 | // Type conversion with error handling 400 | const int_payload = msgpack.Payload.intToPayload(-42); 401 | const uint_result = int_payload.getUint() catch |err| switch (err) { 402 | msgpack.MsgPackError.InvalidType => { 403 | std.debug.print("Cannot convert negative to unsigned\n"); 404 | return; 405 | }, 406 | else => return err, 407 | }; 408 | 409 | // Strict type conversion (no auto-conversion) 410 | const strict_int = payload.asInt() catch |err| { 411 | // Only accepts .int type, rejects .uint even if it fits 412 | return err; 413 | }; 414 | 415 | // Type checking 416 | if (payload.isNil()) { 417 | std.debug.print("Value is nil\n", .{}); 418 | } 419 | if (payload.isNumber()) { 420 | std.debug.print("Value is a number (int/uint/float)\n", .{}); 421 | } 422 | if (payload.isInteger()) { 423 | std.debug.print("Value is an integer (int/uint)\n", .{}); 424 | } 425 | ``` 426 | 427 | ### Security Features (Parsing Untrusted Data) 428 | 429 | The library includes configurable safety limits to protect against malicious or corrupted MessagePack data: 430 | 431 | ```zig 432 | // Default limits (recommended for most use cases) 433 | const Packer = msgpack.Pack( 434 | *Writer, *Reader, 435 | Writer.Error, Reader.Error, 436 | Writer.write, Reader.read, 437 | ); 438 | // Automatically protected against: 439 | // - Deep nesting attacks (max 1000 layers) 440 | // - Large array/map attacks (max 1M elements) 441 | // - Memory exhaustion (max 100MB strings) 442 | 443 | // Custom limits for specific environments 444 | const StrictPacker = msgpack.PackWithLimits( 445 | *Writer, *Reader, 446 | Writer.Error, Reader.Error, 447 | Writer.write, Reader.read, 448 | .{ 449 | .max_depth = 50, // Limit nesting to 50 layers 450 | .max_array_length = 10_000, // Max 10K array elements 451 | .max_map_size = 10_000, // Max 10K map pairs 452 | .max_string_length = 1024 * 1024, // Max 1MB strings 453 | .max_bin_length = 1024 * 1024, // Max 1MB binary blobs 454 | .max_ext_length = 512 * 1024, // Max 512KB extension data 455 | }, 456 | ); 457 | ``` 458 | 459 | **Security Guarantees:** 460 | 461 | - ✅ **Never crashes** on malformed or malicious input 462 | - ✅ **No stack overflow** regardless of nesting depth (iterative parser) 463 | - ✅ **Bounded memory usage** with configurable limits 464 | - ✅ **Fast rejection** of invalid data (no resource exhaustion) 465 | 466 | Possible security errors: 467 | 468 | ```zig 469 | msgpack.MsgPackError.MaxDepthExceeded // Nesting too deep 470 | msgpack.MsgPackError.ArrayTooLarge // Array claims too many elements 471 | msgpack.MsgPackError.MapTooLarge // Map claims too many pairs 472 | msgpack.MsgPackError.StringTooLong // String data too large 473 | msgpack.MsgPackError.BinDataLengthTooLong // Binary blob too large 474 | msgpack.MsgPackError.ExtDataTooLarge // Extension payload too large 475 | ``` 476 | 477 | ## API Overview 478 | 479 | - **`msgpack.Pack`**: The main struct for packing and unpacking MessagePack data with default safety limits. 480 | - **`msgpack.PackWithLimits`**: Create a packer with custom safety limits for specific security requirements. 481 | - **`msgpack.Payload`**: A union that represents any MessagePack type. It provides methods for creating and interacting with different data types (e.g., `mapPayload`, `strToPayload`, `mapGet`). 482 | - **`msgpack.PackerIO`**: (Zig 0.15+) Convenient wrapper for working with `std.io.Reader` and `std.io.Writer`. 483 | - **`msgpack.packIO`**: (Zig 0.15+) Convenience function to create a `PackerIO` instance. 484 | - **`msgpack.ParseLimits`**: Configuration struct for parser safety limits. 485 | - **Constant Structures**: `FixLimits`, `IntBounds`, `FixExtLen`, `TimestampExt`, `MarkerBase` - organized constants for better code clarity. 486 | 487 | ### Type Conversion Methods 488 | 489 | **Lenient conversion** (allows type conversion): 490 | - `getInt()` - uint can be converted to i64 if it fits 491 | - `getUint()` - positive int can be converted to u64 492 | 493 | **Strict conversion** (no type conversion): 494 | - `asInt()`, `asUint()`, `asFloat()`, `asBool()`, `asStr()`, `asBin()` 495 | 496 | **Type checking**: 497 | - `isNil()`, `isNumber()`, `isInteger()` 498 | 499 | ### Map Operations 500 | 501 | **String keys** (backward compatible): 502 | - `mapPut(key: []const u8, value: Payload)` 503 | - `mapGet(key: []const u8) ?Payload` 504 | 505 | **Generic keys** (any Payload type): 506 | - `mapPutGeneric(key: Payload, value: Payload)` 507 | - `mapGetGeneric(key: Payload) ?Payload` 508 | 509 | ## Implementation Notes 510 | 511 | ### Security Architecture 512 | 513 | This library uses an **iterative parser** (not recursive) to provide strong security guarantees: 514 | 515 | **Iterative Parsing:** 516 | 517 | - Parser uses an explicit stack (on heap) instead of recursive function calls 518 | - Stack depth remains constant regardless of input nesting depth 519 | - Prevents stack overflow attacks completely 520 | 521 | **Safety Limits:** 522 | 523 | - All limits are enforced **before** memory allocation 524 | - Invalid input is rejected immediately without resource consumption 525 | - Configurable limits allow tuning for specific environments (embedded, server, etc.) 526 | 527 | **Memory Safety:** 528 | 529 | - All error paths include complete cleanup (`errdefer` + `cleanupParseStack`) 530 | - Zero memory leaks verified by GPA (General Purpose Allocator) in tests 531 | - Safe to parse untrusted data from network, files, or user input 532 | 533 | ### Zig 0.16 Compatibility 534 | 535 | Starting from Zig 0.16, the standard library underwent significant changes to the I/O subsystem. The `std.io.FixedBufferStream` was removed as part of a broader redesign. This library includes a compatibility layer (`src/compat.zig`) that: 536 | 537 | - Provides a `BufferStream` implementation for Zig 0.16+ that mimics the behavior of the old `FixedBufferStream` 538 | - Uses conditional compilation to maintain backward compatibility with Zig 0.14 and 0.15 539 | - Ensures all existing functionality works seamlessly across different Zig versions 540 | 541 | This means you can use the same API regardless of your Zig version, and the library will handle the differences internally. 542 | 543 | ## Testing 544 | 545 | To run the unit tests for this library, use the following command: 546 | 547 | ```sh 548 | zig build test 549 | 550 | # For more detailed test output 551 | zig build test --summary all 552 | ``` 553 | 554 | The comprehensive test suite includes: 555 | 556 | - **87 tests** covering all functionality 557 | - **Malicious data tests:** Verify protection against crafted attacks (billion-element arrays, extreme nesting, etc.) 558 | - **Fuzz tests:** Random input validation ensures no crashes on arbitrary data 559 | - **Large data tests:** Arrays with 1000+ elements, maps with 500+ pairs 560 | - **Memory safety:** Zero leaks verified by strict allocator testing 561 | 562 | ## Benchmarks 563 | 564 | To run performance benchmarks: 565 | 566 | ```sh 567 | # Run benchmarks (default build mode) 568 | zig build bench 569 | 570 | # Run with optimizations for accurate performance measurements 571 | zig build bench -Doptimize=ReleaseFast 572 | ``` 573 | 574 | The benchmark suite includes: 575 | 576 | - Basic types (nil, bool, integers, floats) 577 | - Strings and binary data of various sizes 578 | - Arrays and maps (small, medium, large) 579 | - Extension types and timestamps 580 | - Nested structures and mixed-type payloads 581 | 582 | Output provides throughput (ops/sec) and latency (ns/op) metrics for each operation. 583 | 584 | ## Documentation 585 | 586 | To generate documentation for this library: 587 | 588 | ```sh 589 | zig build docs 590 | ``` 591 | 592 | ## Contributing 593 | 594 | Contributions are welcome! Please feel free to open an issue or submit a pull request. 595 | 596 | ## Performance 597 | 598 | This library is heavily optimized for performance across all supported platforms. The optimizations are architecture-aware and automatically adapt to your target platform at compile time. 599 | 600 | ### Optimization Features 601 | 602 | 1. **CPU Cache Prefetching** 603 | - Platform-specific prefetch instructions (x86: `PREFETCH*`, ARM: `PRFM`) 604 | - Intelligently prefetches data before it's needed for containers ≥256 bytes 605 | - Multi-level cache hints (L1/L2/L3) for optimal cache utilization 606 | - Streaming prefetch for non-temporal data access 607 | 608 | 2. **SIMD Operations** 609 | - Automatic detection of available SIMD features (AVX-512, AVX2, SSE2, NEON) 610 | - Vectorized string comparison (16-64 byte chunks) 611 | - Vectorized memory copying with alignment optimization 612 | - Vectorized byte order conversion (big-endian ↔ little-endian) 613 | - Batch integer array conversions (u32/u64) 614 | 615 | 3. **Memory Alignment Optimization** 616 | - Automatic alignment detection and fast path selection 617 | - Aligned memory reads/writes for supported types 618 | - Memory alignment preprocessing for better SIMD performance 619 | - Large data copy optimization (≥64 bytes) 620 | 621 | 4. **Branch Prediction Optimization** 622 | - Hot path annotations for common cases 623 | - Lookup tables for O(1) marker byte conversion (256-entry precomputed table) 624 | - Switch expressions instead of if-else chains (jump table optimization) 625 | - Optimized error handling paths 626 | 627 | 5. **HashMap-Based Maps** 628 | - O(1) average-case key lookups (vs O(n) linear search) 629 | - Efficient hash function with depth limiting 630 | - Support for any Payload type as keys 631 | - `getOrPut` optimization (single hash computation) 632 | 633 | ### Performance Characteristics 634 | 635 | Measured performance improvements over baseline implementations: 636 | 637 | | Operation Type | Performance Gain | Throughput | Key Optimizations | 638 | | ------------------------ | ---------------- | ----------------------- | ---------------------------------------- | 639 | | Small/Simple Data | 5-10% | ~20M ops/sec | Branch prediction, lookup tables | 640 | | Large Strings (≥256B) | 15-25% | ~2-5 GB/s | Prefetching, SIMD comparison | 641 | | Large Binary (≥256B) | 15-25% | ~3-6 GB/s | Prefetching, SIMD memcpy | 642 | | Integer Arrays (100+) | 10-20% | ~500K-1M arrays/sec | Batch conversion, prefetching | 643 | | Map Lookups (100+ keys) | 50-90% | ~5-10M lookups/sec | HashMap O(1) vs linear O(n) | 644 | | Nested Structures | 8-15% | ~100K-500K structs/sec | Combined optimizations | 645 | | Mixed Type Data | 10-15% | Varies by data | Adaptive optimizations | 646 | 647 | > **Note:** Performance varies by platform, CPU model, data size, and compiler optimization level (`ReleaseFast` vs `ReleaseSafe`). 648 | > Measurements taken on modern CPUs (Intel Core i7/i9, Apple M1/M2, AMD Ryzen). 649 | 650 | ### Platform-Specific Optimizations 651 | 652 | | Platform | SIMD Features | Prefetch Instructions | String Comparison | Memory Copy | 653 | |----------|---------------|----------------------|-------------------|-------------| 654 | | **x86_64 (AVX-512)** | 512-bit vectors | `PREFETCHT0/1/2/NTA` | 64-byte chunks | 64-byte chunks | 655 | | **x86_64 (AVX2)** | 256-bit vectors | `PREFETCHT0/1/2/NTA` | 32-byte chunks | 32-byte chunks | 656 | | **x86_64 (SSE2)** | 128-bit vectors | `PREFETCHT0/1/2/NTA` | 16-byte chunks | 16-byte chunks | 657 | | **ARM64 (NEON)** | 128-bit vectors | `PRFM PLD/PST` | 16-byte chunks | 16-byte chunks | 658 | | **Other** | Scalar fallback | No prefetch | Standard `memcmp` | Standard `memcpy` | 659 | 660 | All optimizations are **compile-time detected** with zero runtime overhead. The library automatically uses the best available features for your target platform. 661 | 662 | ### Running Performance Tests 663 | 664 | ```sh 665 | # Standard benchmark suite 666 | zig build bench -Doptimize=ReleaseFast 667 | 668 | # Sample output: 669 | # Benchmark Name | Iterations | ns/op | ops/sec 670 | # ------------------------------------------------------------------------ 671 | # Nil Write | 1000000 | 45 | 22222222 672 | # Small Int Write | 1000000 | 52 | 19230769 673 | # Large String Write (1KB) | 100000 | 1250 | 800000 674 | # Map Lookup (100 keys) | 500000 | 180 | 5555555 675 | ``` 676 | 677 | ## Related Projects 678 | 679 | - [getty-msgpack](https://git.mzte.de/LordMZTE/getty-msgpack) 680 | - [znvim](https://github.com/jinzhongjia/znvim) 681 | 682 | ## License 683 | 684 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 685 | -------------------------------------------------------------------------------- /src/bench.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const msgpack = @import("msgpack"); 4 | const compat = msgpack.compat; 5 | const Payload = msgpack.Payload; 6 | 7 | const bufferType = compat.BufferStream; 8 | const fixedBufferStream = compat.fixedBufferStream; 9 | 10 | const pack = msgpack.Pack( 11 | *bufferType, 12 | *bufferType, 13 | bufferType.WriteError, 14 | bufferType.ReadError, 15 | bufferType.write, 16 | bufferType.read, 17 | ); 18 | 19 | /// Benchmark timer helper 20 | /// Run a benchmark and print results 21 | fn benchmark( 22 | comptime name: []const u8, 23 | comptime iterations: usize, 24 | comptime func: fn (allocator: std.mem.Allocator) anyerror!void, 25 | ) !void { 26 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 27 | defer _ = gpa.deinit(); 28 | const allocator = gpa.allocator(); 29 | 30 | // Warmup 31 | for (0..10) |_| { 32 | try func(allocator); 33 | } 34 | 35 | // Actual benchmark 36 | var timer = try std.time.Timer.start(); 37 | for (0..iterations) |_| { 38 | try func(allocator); 39 | } 40 | const elapsed_ns = timer.read(); 41 | 42 | const avg_ns = elapsed_ns / iterations; 43 | const ops_per_sec = if (avg_ns > 0) (1_000_000_000 / avg_ns) else 0; 44 | 45 | std.debug.print( 46 | "{s:40} | {d:8} iterations | {d:8} ns/op | {d:8} ops/sec\n", 47 | .{ name, iterations, avg_ns, ops_per_sec }, 48 | ); 49 | } 50 | 51 | // ============================================================================ 52 | // Basic Type Benchmarks 53 | // ============================================================================ 54 | 55 | fn benchNilWrite(allocator: std.mem.Allocator) !void { 56 | _ = allocator; 57 | var arr: [100]u8 = undefined; 58 | var write_buffer = fixedBufferStream(&arr); 59 | var read_buffer = fixedBufferStream(&arr); 60 | var p = pack.init(&write_buffer, &read_buffer); 61 | 62 | try p.write(Payload.nilToPayload()); 63 | } 64 | 65 | fn benchNilRead(allocator: std.mem.Allocator) !void { 66 | const BufferLen = 100; 67 | const State = struct { 68 | var initialized = false; 69 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 70 | }; 71 | 72 | if (!State.initialized) { 73 | var write_buffer = fixedBufferStream(State.buffer[0..]); 74 | var read_buffer = fixedBufferStream(State.buffer[0..]); 75 | var p = pack.init(&write_buffer, &read_buffer); 76 | try p.write(Payload.nilToPayload()); 77 | State.initialized = true; 78 | } 79 | 80 | var write_buffer = fixedBufferStream(State.buffer[0..]); 81 | var read_buffer = fixedBufferStream(State.buffer[0..]); 82 | var p = pack.init(&write_buffer, &read_buffer); 83 | const val = try p.read(allocator); 84 | defer val.free(allocator); 85 | } 86 | 87 | fn benchBoolWrite(allocator: std.mem.Allocator) !void { 88 | _ = allocator; 89 | var arr: [100]u8 = undefined; 90 | var write_buffer = fixedBufferStream(&arr); 91 | var read_buffer = fixedBufferStream(&arr); 92 | var p = pack.init(&write_buffer, &read_buffer); 93 | 94 | try p.write(Payload.boolToPayload(true)); 95 | } 96 | 97 | fn benchBoolRead(allocator: std.mem.Allocator) !void { 98 | const BufferLen = 100; 99 | const State = struct { 100 | var initialized = false; 101 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 102 | }; 103 | 104 | if (!State.initialized) { 105 | var write_buffer = fixedBufferStream(State.buffer[0..]); 106 | var read_buffer = fixedBufferStream(State.buffer[0..]); 107 | var p = pack.init(&write_buffer, &read_buffer); 108 | try p.write(Payload.boolToPayload(true)); 109 | State.initialized = true; 110 | } 111 | 112 | var write_buffer = fixedBufferStream(State.buffer[0..]); 113 | var read_buffer = fixedBufferStream(State.buffer[0..]); 114 | var p = pack.init(&write_buffer, &read_buffer); 115 | const val = try p.read(allocator); 116 | defer val.free(allocator); 117 | } 118 | 119 | fn benchSmallIntWrite(allocator: std.mem.Allocator) !void { 120 | _ = allocator; 121 | var arr: [100]u8 = undefined; 122 | var write_buffer = fixedBufferStream(&arr); 123 | var read_buffer = fixedBufferStream(&arr); 124 | var p = pack.init(&write_buffer, &read_buffer); 125 | 126 | try p.write(Payload.intToPayload(42)); 127 | } 128 | 129 | fn benchSmallIntRead(allocator: std.mem.Allocator) !void { 130 | const BufferLen = 100; 131 | const State = struct { 132 | var initialized = false; 133 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 134 | }; 135 | 136 | if (!State.initialized) { 137 | var write_buffer = fixedBufferStream(State.buffer[0..]); 138 | var read_buffer = fixedBufferStream(State.buffer[0..]); 139 | var p = pack.init(&write_buffer, &read_buffer); 140 | try p.write(Payload.intToPayload(42)); 141 | State.initialized = true; 142 | } 143 | 144 | var write_buffer = fixedBufferStream(State.buffer[0..]); 145 | var read_buffer = fixedBufferStream(State.buffer[0..]); 146 | var p = pack.init(&write_buffer, &read_buffer); 147 | const val = try p.read(allocator); 148 | defer val.free(allocator); 149 | } 150 | 151 | fn benchLargeIntWrite(allocator: std.mem.Allocator) !void { 152 | _ = allocator; 153 | var arr: [100]u8 = undefined; 154 | var write_buffer = fixedBufferStream(&arr); 155 | var read_buffer = fixedBufferStream(&arr); 156 | var p = pack.init(&write_buffer, &read_buffer); 157 | 158 | try p.write(Payload.intToPayload(9223372036854775807)); 159 | } 160 | 161 | fn benchLargeIntRead(allocator: std.mem.Allocator) !void { 162 | const BufferLen = 100; 163 | const State = struct { 164 | var initialized = false; 165 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 166 | }; 167 | 168 | if (!State.initialized) { 169 | var write_buffer = fixedBufferStream(State.buffer[0..]); 170 | var read_buffer = fixedBufferStream(State.buffer[0..]); 171 | var p = pack.init(&write_buffer, &read_buffer); 172 | try p.write(Payload.intToPayload(9223372036854775807)); 173 | State.initialized = true; 174 | } 175 | 176 | var write_buffer = fixedBufferStream(State.buffer[0..]); 177 | var read_buffer = fixedBufferStream(State.buffer[0..]); 178 | var p = pack.init(&write_buffer, &read_buffer); 179 | const val = try p.read(allocator); 180 | defer val.free(allocator); 181 | } 182 | 183 | fn benchFloatWrite(allocator: std.mem.Allocator) !void { 184 | _ = allocator; 185 | var arr: [100]u8 = undefined; 186 | var write_buffer = fixedBufferStream(&arr); 187 | var read_buffer = fixedBufferStream(&arr); 188 | var p = pack.init(&write_buffer, &read_buffer); 189 | 190 | try p.write(Payload.floatToPayload(3.14159265359)); 191 | } 192 | 193 | fn benchFloatRead(allocator: std.mem.Allocator) !void { 194 | const BufferLen = 100; 195 | const State = struct { 196 | var initialized = false; 197 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 198 | }; 199 | 200 | if (!State.initialized) { 201 | var write_buffer = fixedBufferStream(State.buffer[0..]); 202 | var read_buffer = fixedBufferStream(State.buffer[0..]); 203 | var p = pack.init(&write_buffer, &read_buffer); 204 | try p.write(Payload.floatToPayload(3.14159265359)); 205 | State.initialized = true; 206 | } 207 | 208 | var write_buffer = fixedBufferStream(State.buffer[0..]); 209 | var read_buffer = fixedBufferStream(State.buffer[0..]); 210 | var p = pack.init(&write_buffer, &read_buffer); 211 | const val = try p.read(allocator); 212 | defer val.free(allocator); 213 | } 214 | 215 | // ============================================================================ 216 | // String Benchmarks 217 | // ============================================================================ 218 | 219 | fn benchShortStrWrite(allocator: std.mem.Allocator) !void { 220 | var arr: [1000]u8 = undefined; 221 | var write_buffer = fixedBufferStream(&arr); 222 | var read_buffer = fixedBufferStream(&arr); 223 | var p = pack.init(&write_buffer, &read_buffer); 224 | 225 | const str = try Payload.strToPayload("hello", allocator); 226 | defer str.free(allocator); 227 | try p.write(str); 228 | } 229 | 230 | fn benchShortStrRead(allocator: std.mem.Allocator) !void { 231 | const BufferLen = 1000; 232 | const State = struct { 233 | var initialized = false; 234 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 235 | }; 236 | 237 | if (!State.initialized) { 238 | var write_buffer = fixedBufferStream(State.buffer[0..]); 239 | var read_buffer = fixedBufferStream(State.buffer[0..]); 240 | var p = pack.init(&write_buffer, &read_buffer); 241 | 242 | const str = try Payload.strToPayload("hello", allocator); 243 | defer str.free(allocator); 244 | try p.write(str); 245 | 246 | State.initialized = true; 247 | } 248 | 249 | var write_buffer = fixedBufferStream(State.buffer[0..]); 250 | var read_buffer = fixedBufferStream(State.buffer[0..]); 251 | var p = pack.init(&write_buffer, &read_buffer); 252 | const val = try p.read(allocator); 253 | defer val.free(allocator); 254 | } 255 | 256 | fn benchMediumStrWrite(allocator: std.mem.Allocator) !void { 257 | var arr: [2000]u8 = undefined; 258 | var write_buffer = fixedBufferStream(&arr); 259 | var read_buffer = fixedBufferStream(&arr); 260 | var p = pack.init(&write_buffer, &read_buffer); 261 | 262 | const test_str = "This is a medium length string for benchmarking MessagePack performance. " ** 4; 263 | const str = try Payload.strToPayload(test_str, allocator); 264 | defer str.free(allocator); 265 | try p.write(str); 266 | } 267 | 268 | fn benchMediumStrRead(allocator: std.mem.Allocator) !void { 269 | const BufferLen = 2000; 270 | const State = struct { 271 | var initialized = false; 272 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 273 | }; 274 | 275 | if (!State.initialized) { 276 | var write_buffer = fixedBufferStream(State.buffer[0..]); 277 | var read_buffer = fixedBufferStream(State.buffer[0..]); 278 | var p = pack.init(&write_buffer, &read_buffer); 279 | 280 | const test_str = "This is a medium length string for benchmarking MessagePack performance. " ** 4; 281 | const str = try Payload.strToPayload(test_str, allocator); 282 | defer str.free(allocator); 283 | try p.write(str); 284 | 285 | State.initialized = true; 286 | } 287 | 288 | var write_buffer = fixedBufferStream(State.buffer[0..]); 289 | var read_buffer = fixedBufferStream(State.buffer[0..]); 290 | var p = pack.init(&write_buffer, &read_buffer); 291 | const val = try p.read(allocator); 292 | defer val.free(allocator); 293 | } 294 | 295 | // ============================================================================ 296 | // Binary Data Benchmarks 297 | // ============================================================================ 298 | 299 | fn benchSmallBinWrite(allocator: std.mem.Allocator) !void { 300 | var arr: [1000]u8 = undefined; 301 | var write_buffer = fixedBufferStream(&arr); 302 | var read_buffer = fixedBufferStream(&arr); 303 | var p = pack.init(&write_buffer, &read_buffer); 304 | 305 | var data = [_]u8{1} ** 32; 306 | const bin = try Payload.binToPayload(&data, allocator); 307 | defer bin.free(allocator); 308 | try p.write(bin); 309 | } 310 | 311 | fn benchSmallBinRead(allocator: std.mem.Allocator) !void { 312 | const BufferLen = 1000; 313 | const State = struct { 314 | var initialized = false; 315 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 316 | }; 317 | 318 | if (!State.initialized) { 319 | var write_buffer = fixedBufferStream(State.buffer[0..]); 320 | var read_buffer = fixedBufferStream(State.buffer[0..]); 321 | var p = pack.init(&write_buffer, &read_buffer); 322 | 323 | var data = [_]u8{1} ** 32; 324 | const bin = try Payload.binToPayload(&data, allocator); 325 | defer bin.free(allocator); 326 | try p.write(bin); 327 | 328 | State.initialized = true; 329 | } 330 | 331 | var write_buffer = fixedBufferStream(State.buffer[0..]); 332 | var read_buffer = fixedBufferStream(State.buffer[0..]); 333 | var p = pack.init(&write_buffer, &read_buffer); 334 | const val = try p.read(allocator); 335 | defer val.free(allocator); 336 | } 337 | 338 | fn benchLargeBinWrite(allocator: std.mem.Allocator) !void { 339 | var arr: [2000]u8 = undefined; 340 | var write_buffer = fixedBufferStream(&arr); 341 | var read_buffer = fixedBufferStream(&arr); 342 | var p = pack.init(&write_buffer, &read_buffer); 343 | 344 | const data = try allocator.alloc(u8, 1024); 345 | defer allocator.free(data); 346 | @memset(data, 0x42); 347 | 348 | const bin = try Payload.binToPayload(data, allocator); 349 | defer bin.free(allocator); 350 | try p.write(bin); 351 | } 352 | 353 | fn benchLargeBinRead(allocator: std.mem.Allocator) !void { 354 | const BufferLen = 2000; 355 | const State = struct { 356 | var initialized = false; 357 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 358 | }; 359 | 360 | if (!State.initialized) { 361 | var write_buffer = fixedBufferStream(State.buffer[0..]); 362 | var read_buffer = fixedBufferStream(State.buffer[0..]); 363 | var p = pack.init(&write_buffer, &read_buffer); 364 | 365 | const data = try allocator.alloc(u8, 1024); 366 | defer allocator.free(data); 367 | @memset(data, 0x42); 368 | 369 | const bin = try Payload.binToPayload(data, allocator); 370 | defer bin.free(allocator); 371 | try p.write(bin); 372 | 373 | State.initialized = true; 374 | } 375 | 376 | var write_buffer = fixedBufferStream(State.buffer[0..]); 377 | var read_buffer = fixedBufferStream(State.buffer[0..]); 378 | var p = pack.init(&write_buffer, &read_buffer); 379 | const val = try p.read(allocator); 380 | defer val.free(allocator); 381 | } 382 | 383 | // ============================================================================ 384 | // Array Benchmarks 385 | // ============================================================================ 386 | 387 | fn benchSmallArrayWrite(allocator: std.mem.Allocator) !void { 388 | var arr: [1000]u8 = undefined; 389 | var write_buffer = fixedBufferStream(&arr); 390 | var read_buffer = fixedBufferStream(&arr); 391 | var p = pack.init(&write_buffer, &read_buffer); 392 | 393 | var array = try Payload.arrPayload(10, allocator); 394 | defer array.free(allocator); 395 | for (0..10) |i| { 396 | try array.setArrElement(i, Payload.intToPayload(@intCast(i))); 397 | } 398 | try p.write(array); 399 | } 400 | 401 | fn benchSmallArrayRead(allocator: std.mem.Allocator) !void { 402 | const BufferLen = 1000; 403 | const State = struct { 404 | var initialized = false; 405 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 406 | }; 407 | 408 | if (!State.initialized) { 409 | var write_buffer = fixedBufferStream(State.buffer[0..]); 410 | var read_buffer = fixedBufferStream(State.buffer[0..]); 411 | var p = pack.init(&write_buffer, &read_buffer); 412 | 413 | var array = try Payload.arrPayload(10, allocator); 414 | defer array.free(allocator); 415 | for (0..10) |i| { 416 | try array.setArrElement(i, Payload.intToPayload(@intCast(i))); 417 | } 418 | try p.write(array); 419 | 420 | State.initialized = true; 421 | } 422 | 423 | var write_buffer = fixedBufferStream(State.buffer[0..]); 424 | var read_buffer = fixedBufferStream(State.buffer[0..]); 425 | var p = pack.init(&write_buffer, &read_buffer); 426 | const val = try p.read(allocator); 427 | defer val.free(allocator); 428 | } 429 | 430 | fn benchMediumArrayWrite(allocator: std.mem.Allocator) !void { 431 | var arr: [5000]u8 = undefined; 432 | var write_buffer = fixedBufferStream(&arr); 433 | var read_buffer = fixedBufferStream(&arr); 434 | var p = pack.init(&write_buffer, &read_buffer); 435 | 436 | var array = try Payload.arrPayload(100, allocator); 437 | defer array.free(allocator); 438 | for (0..100) |i| { 439 | try array.setArrElement(i, Payload.intToPayload(@intCast(i))); 440 | } 441 | try p.write(array); 442 | } 443 | 444 | fn benchMediumArrayRead(allocator: std.mem.Allocator) !void { 445 | const BufferLen = 5000; 446 | const State = struct { 447 | var initialized = false; 448 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 449 | }; 450 | 451 | if (!State.initialized) { 452 | var write_buffer = fixedBufferStream(State.buffer[0..]); 453 | var read_buffer = fixedBufferStream(State.buffer[0..]); 454 | var p = pack.init(&write_buffer, &read_buffer); 455 | 456 | var array = try Payload.arrPayload(100, allocator); 457 | defer array.free(allocator); 458 | for (0..100) |i| { 459 | try array.setArrElement(i, Payload.intToPayload(@intCast(i))); 460 | } 461 | try p.write(array); 462 | 463 | State.initialized = true; 464 | } 465 | 466 | var write_buffer = fixedBufferStream(State.buffer[0..]); 467 | var read_buffer = fixedBufferStream(State.buffer[0..]); 468 | var p = pack.init(&write_buffer, &read_buffer); 469 | const val = try p.read(allocator); 470 | defer val.free(allocator); 471 | } 472 | 473 | // ============================================================================ 474 | // Map Benchmarks 475 | // ============================================================================ 476 | 477 | fn benchSmallMapWrite(allocator: std.mem.Allocator) !void { 478 | var arr: [2000]u8 = undefined; 479 | var write_buffer = fixedBufferStream(&arr); 480 | var read_buffer = fixedBufferStream(&arr); 481 | var p = pack.init(&write_buffer, &read_buffer); 482 | 483 | var map = Payload.mapPayload(allocator); 484 | defer map.free(allocator); 485 | 486 | for (0..10) |i| { 487 | const key = try std.fmt.allocPrint(allocator, "key{d}", .{i}); 488 | defer allocator.free(key); 489 | try map.mapPut(key, Payload.intToPayload(@intCast(i))); 490 | } 491 | 492 | try p.write(map); 493 | } 494 | 495 | fn benchSmallMapRead(allocator: std.mem.Allocator) !void { 496 | const BufferLen = 2000; 497 | const State = struct { 498 | var initialized = false; 499 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 500 | }; 501 | 502 | if (!State.initialized) { 503 | var write_buffer = fixedBufferStream(State.buffer[0..]); 504 | var read_buffer = fixedBufferStream(State.buffer[0..]); 505 | var p = pack.init(&write_buffer, &read_buffer); 506 | 507 | var map = Payload.mapPayload(allocator); 508 | defer map.free(allocator); 509 | 510 | for (0..10) |i| { 511 | const key = try std.fmt.allocPrint(allocator, "key{d}", .{i}); 512 | defer allocator.free(key); 513 | try map.mapPut(key, Payload.intToPayload(@intCast(i))); 514 | } 515 | 516 | try p.write(map); 517 | 518 | State.initialized = true; 519 | } 520 | 521 | var write_buffer = fixedBufferStream(State.buffer[0..]); 522 | var read_buffer = fixedBufferStream(State.buffer[0..]); 523 | var p = pack.init(&write_buffer, &read_buffer); 524 | const val = try p.read(allocator); 525 | defer val.free(allocator); 526 | } 527 | 528 | fn benchMediumMapWrite(allocator: std.mem.Allocator) !void { 529 | var arr: [10000]u8 = undefined; 530 | var write_buffer = fixedBufferStream(&arr); 531 | var read_buffer = fixedBufferStream(&arr); 532 | var p = pack.init(&write_buffer, &read_buffer); 533 | 534 | var map = Payload.mapPayload(allocator); 535 | defer map.free(allocator); 536 | 537 | for (0..50) |i| { 538 | const key = try std.fmt.allocPrint(allocator, "key{d}", .{i}); 539 | defer allocator.free(key); 540 | try map.mapPut(key, Payload.intToPayload(@intCast(i))); 541 | } 542 | 543 | try p.write(map); 544 | } 545 | 546 | fn benchMediumMapRead(allocator: std.mem.Allocator) !void { 547 | const BufferLen = 10000; 548 | const State = struct { 549 | var initialized = false; 550 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 551 | }; 552 | 553 | if (!State.initialized) { 554 | var write_buffer = fixedBufferStream(State.buffer[0..]); 555 | var read_buffer = fixedBufferStream(State.buffer[0..]); 556 | var p = pack.init(&write_buffer, &read_buffer); 557 | 558 | var map = Payload.mapPayload(allocator); 559 | defer map.free(allocator); 560 | 561 | for (0..50) |i| { 562 | const key = try std.fmt.allocPrint(allocator, "key{d}", .{i}); 563 | defer allocator.free(key); 564 | try map.mapPut(key, Payload.intToPayload(@intCast(i))); 565 | } 566 | 567 | try p.write(map); 568 | 569 | State.initialized = true; 570 | } 571 | 572 | var write_buffer = fixedBufferStream(State.buffer[0..]); 573 | var read_buffer = fixedBufferStream(State.buffer[0..]); 574 | var p = pack.init(&write_buffer, &read_buffer); 575 | const val = try p.read(allocator); 576 | defer val.free(allocator); 577 | } 578 | 579 | // ============================================================================ 580 | // Extension Type Benchmarks 581 | // ============================================================================ 582 | 583 | fn benchExtWrite(allocator: std.mem.Allocator) !void { 584 | var arr: [1000]u8 = undefined; 585 | var write_buffer = fixedBufferStream(&arr); 586 | var read_buffer = fixedBufferStream(&arr); 587 | var p = pack.init(&write_buffer, &read_buffer); 588 | 589 | var data = [_]u8{1} ** 16; 590 | const ext = try Payload.extToPayload(42, &data, allocator); 591 | defer ext.free(allocator); 592 | try p.write(ext); 593 | } 594 | 595 | fn benchExtRead(allocator: std.mem.Allocator) !void { 596 | const BufferLen = 1000; 597 | const State = struct { 598 | var initialized = false; 599 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 600 | }; 601 | 602 | if (!State.initialized) { 603 | var write_buffer = fixedBufferStream(State.buffer[0..]); 604 | var read_buffer = fixedBufferStream(State.buffer[0..]); 605 | var p = pack.init(&write_buffer, &read_buffer); 606 | 607 | var data = [_]u8{1} ** 16; 608 | const ext = try Payload.extToPayload(42, &data, allocator); 609 | defer ext.free(allocator); 610 | try p.write(ext); 611 | 612 | State.initialized = true; 613 | } 614 | 615 | var write_buffer = fixedBufferStream(State.buffer[0..]); 616 | var read_buffer = fixedBufferStream(State.buffer[0..]); 617 | var p = pack.init(&write_buffer, &read_buffer); 618 | const val = try p.read(allocator); 619 | defer val.free(allocator); 620 | } 621 | 622 | // ============================================================================ 623 | // Timestamp Benchmarks 624 | // ============================================================================ 625 | 626 | fn benchTimestamp32Write(allocator: std.mem.Allocator) !void { 627 | _ = allocator; 628 | var arr: [1000]u8 = undefined; 629 | var write_buffer = fixedBufferStream(&arr); 630 | var read_buffer = fixedBufferStream(&arr); 631 | var p = pack.init(&write_buffer, &read_buffer); 632 | 633 | const ts = Payload.timestampFromSeconds(1234567890); 634 | try p.write(ts); 635 | } 636 | 637 | fn benchTimestamp32Read(allocator: std.mem.Allocator) !void { 638 | const BufferLen = 1000; 639 | const State = struct { 640 | var initialized = false; 641 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 642 | }; 643 | 644 | if (!State.initialized) { 645 | var write_buffer = fixedBufferStream(State.buffer[0..]); 646 | var read_buffer = fixedBufferStream(State.buffer[0..]); 647 | var p = pack.init(&write_buffer, &read_buffer); 648 | 649 | const ts = Payload.timestampFromSeconds(1234567890); 650 | try p.write(ts); 651 | 652 | State.initialized = true; 653 | } 654 | 655 | var write_buffer = fixedBufferStream(State.buffer[0..]); 656 | var read_buffer = fixedBufferStream(State.buffer[0..]); 657 | var p = pack.init(&write_buffer, &read_buffer); 658 | const val = try p.read(allocator); 659 | defer val.free(allocator); 660 | } 661 | 662 | fn benchTimestamp64Write(allocator: std.mem.Allocator) !void { 663 | _ = allocator; 664 | var arr: [1000]u8 = undefined; 665 | var write_buffer = fixedBufferStream(&arr); 666 | var read_buffer = fixedBufferStream(&arr); 667 | var p = pack.init(&write_buffer, &read_buffer); 668 | 669 | const ts = Payload.timestampToPayload(1234567890, 123456789); 670 | try p.write(ts); 671 | } 672 | 673 | fn benchTimestamp64Read(allocator: std.mem.Allocator) !void { 674 | const BufferLen = 1000; 675 | const State = struct { 676 | var initialized = false; 677 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 678 | }; 679 | 680 | if (!State.initialized) { 681 | var write_buffer = fixedBufferStream(State.buffer[0..]); 682 | var read_buffer = fixedBufferStream(State.buffer[0..]); 683 | var p = pack.init(&write_buffer, &read_buffer); 684 | 685 | const ts = Payload.timestampToPayload(1234567890, 123456789); 686 | try p.write(ts); 687 | 688 | State.initialized = true; 689 | } 690 | 691 | var write_buffer = fixedBufferStream(State.buffer[0..]); 692 | var read_buffer = fixedBufferStream(State.buffer[0..]); 693 | var p = pack.init(&write_buffer, &read_buffer); 694 | const val = try p.read(allocator); 695 | defer val.free(allocator); 696 | } 697 | 698 | // ============================================================================ 699 | // Complex Structure Benchmarks 700 | // ============================================================================ 701 | 702 | fn benchNestedStructureWrite(allocator: std.mem.Allocator) !void { 703 | var arr: [10000]u8 = undefined; 704 | var write_buffer = fixedBufferStream(&arr); 705 | var read_buffer = fixedBufferStream(&arr); 706 | var p = pack.init(&write_buffer, &read_buffer); 707 | 708 | // Create: {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]} 709 | var root = Payload.mapPayload(allocator); 710 | defer root.free(allocator); 711 | 712 | var users = try Payload.arrPayload(2, allocator); 713 | 714 | var user1 = Payload.mapPayload(allocator); 715 | try user1.mapPut("id", Payload.intToPayload(1)); 716 | try user1.mapPut("name", try Payload.strToPayload("Alice", allocator)); 717 | try users.setArrElement(0, user1); 718 | 719 | var user2 = Payload.mapPayload(allocator); 720 | try user2.mapPut("id", Payload.intToPayload(2)); 721 | try user2.mapPut("name", try Payload.strToPayload("Bob", allocator)); 722 | try users.setArrElement(1, user2); 723 | 724 | try root.mapPut("users", users); 725 | try p.write(root); 726 | } 727 | 728 | fn benchNestedStructureRead(allocator: std.mem.Allocator) !void { 729 | const BufferLen = 10000; 730 | const State = struct { 731 | var initialized = false; 732 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 733 | }; 734 | 735 | if (!State.initialized) { 736 | var write_buffer = fixedBufferStream(State.buffer[0..]); 737 | var read_buffer = fixedBufferStream(State.buffer[0..]); 738 | var p = pack.init(&write_buffer, &read_buffer); 739 | 740 | var root = Payload.mapPayload(allocator); 741 | defer root.free(allocator); 742 | 743 | var users = try Payload.arrPayload(2, allocator); 744 | 745 | var user1 = Payload.mapPayload(allocator); 746 | try user1.mapPut("id", Payload.intToPayload(1)); 747 | try user1.mapPut("name", try Payload.strToPayload("Alice", allocator)); 748 | try users.setArrElement(0, user1); 749 | 750 | var user2 = Payload.mapPayload(allocator); 751 | try user2.mapPut("id", Payload.intToPayload(2)); 752 | try user2.mapPut("name", try Payload.strToPayload("Bob", allocator)); 753 | try users.setArrElement(1, user2); 754 | 755 | try root.mapPut("users", users); 756 | try p.write(root); 757 | 758 | State.initialized = true; 759 | } 760 | 761 | var write_buffer = fixedBufferStream(State.buffer[0..]); 762 | var read_buffer = fixedBufferStream(State.buffer[0..]); 763 | var p = pack.init(&write_buffer, &read_buffer); 764 | const val = try p.read(allocator); 765 | defer val.free(allocator); 766 | } 767 | 768 | fn benchMixedTypesWrite(allocator: std.mem.Allocator) !void { 769 | var arr: [5000]u8 = undefined; 770 | var write_buffer = fixedBufferStream(&arr); 771 | var read_buffer = fixedBufferStream(&arr); 772 | var p = pack.init(&write_buffer, &read_buffer); 773 | 774 | var mixed = try Payload.arrPayload(10, allocator); 775 | defer mixed.free(allocator); 776 | 777 | try mixed.setArrElement(0, Payload.nilToPayload()); 778 | try mixed.setArrElement(1, Payload.boolToPayload(true)); 779 | try mixed.setArrElement(2, Payload.intToPayload(-100)); 780 | try mixed.setArrElement(3, Payload.uintToPayload(200)); 781 | try mixed.setArrElement(4, Payload.floatToPayload(3.14)); 782 | try mixed.setArrElement(5, try Payload.strToPayload("hello", allocator)); 783 | 784 | var bin_data = [_]u8{1} ** 8; 785 | try mixed.setArrElement(6, try Payload.binToPayload(&bin_data, allocator)); 786 | 787 | var inner_arr = try Payload.arrPayload(2, allocator); 788 | try inner_arr.setArrElement(0, Payload.intToPayload(1)); 789 | try inner_arr.setArrElement(1, Payload.intToPayload(2)); 790 | try mixed.setArrElement(7, inner_arr); 791 | 792 | var inner_map = Payload.mapPayload(allocator); 793 | try inner_map.mapPut("key", Payload.intToPayload(42)); 794 | try mixed.setArrElement(8, inner_map); 795 | 796 | try mixed.setArrElement(9, Payload.timestampFromSeconds(1000000)); 797 | 798 | try p.write(mixed); 799 | } 800 | 801 | fn benchMixedTypesRead(allocator: std.mem.Allocator) !void { 802 | const BufferLen = 5000; 803 | const State = struct { 804 | var initialized = false; 805 | var buffer: [BufferLen]u8 = [_]u8{0} ** BufferLen; 806 | }; 807 | 808 | if (!State.initialized) { 809 | var write_buffer = fixedBufferStream(State.buffer[0..]); 810 | var read_buffer = fixedBufferStream(State.buffer[0..]); 811 | var p = pack.init(&write_buffer, &read_buffer); 812 | 813 | var mixed = try Payload.arrPayload(10, allocator); 814 | defer mixed.free(allocator); 815 | 816 | try mixed.setArrElement(0, Payload.nilToPayload()); 817 | try mixed.setArrElement(1, Payload.boolToPayload(true)); 818 | try mixed.setArrElement(2, Payload.intToPayload(-100)); 819 | try mixed.setArrElement(3, Payload.uintToPayload(200)); 820 | try mixed.setArrElement(4, Payload.floatToPayload(3.14)); 821 | try mixed.setArrElement(5, try Payload.strToPayload("hello", allocator)); 822 | 823 | var bin_data = [_]u8{1} ** 8; 824 | try mixed.setArrElement(6, try Payload.binToPayload(&bin_data, allocator)); 825 | 826 | var inner_arr = try Payload.arrPayload(2, allocator); 827 | try inner_arr.setArrElement(0, Payload.intToPayload(1)); 828 | try inner_arr.setArrElement(1, Payload.intToPayload(2)); 829 | try mixed.setArrElement(7, inner_arr); 830 | 831 | var inner_map = Payload.mapPayload(allocator); 832 | try inner_map.mapPut("key", Payload.intToPayload(42)); 833 | try mixed.setArrElement(8, inner_map); 834 | 835 | try mixed.setArrElement(9, Payload.timestampFromSeconds(1000000)); 836 | 837 | try p.write(mixed); 838 | 839 | State.initialized = true; 840 | } 841 | 842 | var write_buffer = fixedBufferStream(State.buffer[0..]); 843 | var read_buffer = fixedBufferStream(State.buffer[0..]); 844 | var p = pack.init(&write_buffer, &read_buffer); 845 | const val = try p.read(allocator); 846 | defer val.free(allocator); 847 | } 848 | 849 | // ============================================================================ 850 | // Main Benchmark Runner 851 | // ============================================================================ 852 | 853 | pub fn main() !void { 854 | std.debug.print("\n", .{}); 855 | std.debug.print("=" ** 80 ++ "\n", .{}); 856 | std.debug.print("MessagePack Benchmark Suite\n", .{}); 857 | std.debug.print("=" ** 80 ++ "\n\n", .{}); 858 | 859 | // Basic Types 860 | std.debug.print("Basic Types:\n", .{}); 861 | std.debug.print("-" ** 80 ++ "\n", .{}); 862 | try benchmark("Nil Write", 1000000, benchNilWrite); 863 | try benchmark("Nil Read", 1000000, benchNilRead); 864 | try benchmark("Bool Write", 1000000, benchBoolWrite); 865 | try benchmark("Bool Read", 1000000, benchBoolRead); 866 | try benchmark("Small Int Write", 1000000, benchSmallIntWrite); 867 | try benchmark("Small Int Read", 1000000, benchSmallIntRead); 868 | try benchmark("Large Int Write", 1000000, benchLargeIntWrite); 869 | try benchmark("Large Int Read", 1000000, benchLargeIntRead); 870 | try benchmark("Float Write", 1000000, benchFloatWrite); 871 | try benchmark("Float Read", 1000000, benchFloatRead); 872 | std.debug.print("\n", .{}); 873 | 874 | // Strings 875 | std.debug.print("Strings:\n", .{}); 876 | std.debug.print("-" ** 80 ++ "\n", .{}); 877 | try benchmark("Short String Write (5 bytes)", 500000, benchShortStrWrite); 878 | try benchmark("Short String Read (5 bytes)", 500000, benchShortStrRead); 879 | try benchmark("Medium String Write (~300 bytes)", 100000, benchMediumStrWrite); 880 | try benchmark("Medium String Read (~300 bytes)", 100000, benchMediumStrRead); 881 | std.debug.print("\n", .{}); 882 | 883 | // Binary Data 884 | std.debug.print("Binary Data:\n", .{}); 885 | std.debug.print("-" ** 80 ++ "\n", .{}); 886 | try benchmark("Small Binary Write (32 bytes)", 500000, benchSmallBinWrite); 887 | try benchmark("Small Binary Read (32 bytes)", 500000, benchSmallBinRead); 888 | try benchmark("Large Binary Write (1KB)", 100000, benchLargeBinWrite); 889 | try benchmark("Large Binary Read (1KB)", 100000, benchLargeBinRead); 890 | std.debug.print("\n", .{}); 891 | 892 | // Arrays 893 | std.debug.print("Arrays:\n", .{}); 894 | std.debug.print("-" ** 80 ++ "\n", .{}); 895 | try benchmark("Small Array Write (10 elements)", 100000, benchSmallArrayWrite); 896 | try benchmark("Small Array Read (10 elements)", 100000, benchSmallArrayRead); 897 | try benchmark("Medium Array Write (100 elements)", 50000, benchMediumArrayWrite); 898 | try benchmark("Medium Array Read (100 elements)", 50000, benchMediumArrayRead); 899 | std.debug.print("\n", .{}); 900 | 901 | // Maps 902 | std.debug.print("Maps:\n", .{}); 903 | std.debug.print("-" ** 80 ++ "\n", .{}); 904 | try benchmark("Small Map Write (10 entries)", 100000, benchSmallMapWrite); 905 | try benchmark("Small Map Read (10 entries)", 100000, benchSmallMapRead); 906 | try benchmark("Medium Map Write (50 entries)", 50000, benchMediumMapWrite); 907 | try benchmark("Medium Map Read (50 entries)", 50000, benchMediumMapRead); 908 | std.debug.print("\n", .{}); 909 | 910 | // Extension Types 911 | std.debug.print("Extension Types:\n", .{}); 912 | std.debug.print("-" ** 80 ++ "\n", .{}); 913 | try benchmark("EXT Write (16 bytes)", 500000, benchExtWrite); 914 | try benchmark("EXT Read (16 bytes)", 500000, benchExtRead); 915 | std.debug.print("\n", .{}); 916 | 917 | // Timestamps 918 | std.debug.print("Timestamps:\n", .{}); 919 | std.debug.print("-" ** 80 ++ "\n", .{}); 920 | try benchmark("Timestamp32 Write", 1000000, benchTimestamp32Write); 921 | try benchmark("Timestamp32 Read", 1000000, benchTimestamp32Read); 922 | try benchmark("Timestamp64 Write", 1000000, benchTimestamp64Write); 923 | try benchmark("Timestamp64 Read", 1000000, benchTimestamp64Read); 924 | std.debug.print("\n", .{}); 925 | 926 | // Complex Structures 927 | std.debug.print("Complex Structures:\n", .{}); 928 | std.debug.print("-" ** 80 ++ "\n", .{}); 929 | try benchmark("Nested Structure Write", 50000, benchNestedStructureWrite); 930 | try benchmark("Nested Structure Read", 50000, benchNestedStructureRead); 931 | try benchmark("Mixed Types Write", 50000, benchMixedTypesWrite); 932 | try benchmark("Mixed Types Read", 50000, benchMixedTypesRead); 933 | std.debug.print("\n", .{}); 934 | 935 | std.debug.print("=" ** 80 ++ "\n", .{}); 936 | std.debug.print("Benchmark Complete\n", .{}); 937 | std.debug.print("=" ** 80 ++ "\n", .{}); 938 | } 939 | --------------------------------------------------------------------------------