diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index f79f82e..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Publish to pub.dev - -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+*' - -jobs: - publish: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Dart SDK - uses: dart-lang/setup-dart@v1 - with: - sdk: stable - - - name: Install dependencies - run: dart pub get - - - name: Verify package - run: dart pub publish --dry-run - - - name: Publish package - uses: k-paxian/dart-package-publisher@v1.6 - with: - credentialJson: ${{ secrets.PUB_CREDENTIALS }} - flutter: false - skipTests: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2e1523..30068b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,9 @@ on: branches: [ main, develop ] workflow_dispatch: +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: test: runs-on: ${{ matrix.os }} @@ -53,3 +56,59 @@ jobs: with: file: ./coverage/lcov.info fail_ci_if_error: false + + benchmark: + needs: test + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Dart SDK + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Install dependencies + run: dart pub get + + - name: Run Benchmarks (JIT) + run: | + echo "### ๐Ÿš€ JIT Benchmarks (Dart VM)" >> $GITHUB_STEP_SUMMARY + echo '```text' >> $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor jit --target test/performance/serialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor jit --target test/performance/deserialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor jit --target test/performance/strings_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor jit --target test/performance/pool_bench.dart | tee -a $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Run Benchmarks (AOT) + run: | + echo "### โšก AOT Benchmarks (Native)" >> $GITHUB_STEP_SUMMARY + echo '```text' >> $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor aot --target test/performance/serialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor aot --target test/performance/deserialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor aot --target test/performance/strings_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor aot --target test/performance/pool_bench.dart | tee -a $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Run Benchmarks (JS) + run: | + echo "### ๐ŸŒ JS Benchmarks (V8)" >> $GITHUB_STEP_SUMMARY + echo '```text' >> $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor js --target test/performance/serialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor js --target test/performance/deserialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor js --target test/performance/strings_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor js --target test/performance/pool_bench.dart | tee -a $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Run Benchmarks (WASM) + run: | + echo "### โš™๏ธ WASM Benchmarks" >> $GITHUB_STEP_SUMMARY + echo '```text' >> $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor wasm --target test/performance/serialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor wasm --target test/performance/deserialization_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor wasm --target test/performance/strings_bench.dart | tee -a $GITHUB_STEP_SUMMARY + dart run benchmark_harness:bench --flavor wasm --target test/performance/pool_bench.dart | tee -a $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/CHANGELOG.md b/CHANGELOG.md index dbaf15e..852f9fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 3.1.0 + +- **feat**: Added `BinaryWriterPool.withWriter()` for safer and more concise object pool usage. +- **feat**: Added modern API features for a more idiomatic experience: + - `BinaryReader`: Added `operator []` for absolute byte access (e.g., `reader[0]`). + - `BinaryReader`: Added `call()` method for shorthand byte reading (e.g., `reader(10)`). + - `BinaryWriter`: Added `call()` method for shorthand byte writing (e.g., `writer([1, 2, 3])`). +- **performance**: Optimized `writeVarString` with a high-performance one-pass "optimistic shift" algorithm (~30% faster). +- **performance**: Unrolled `readVarUint` loop for the first 3 bytes, resulting in ~25% speedup in AOT mode. +- **performance**: Applied Fast Path / Slow Path optimization to buffer expansion logic for better inlining. +- **improvement**: Enhanced `BinaryWriterPool.clear()` with explicit buffer reference discarding to assist GC. +- **fix**: Added missing `ensureSize` check in `BinaryWriterPool.acquire` to guarantee buffer capacity when reusing writers. +- **improvement**: Updated lint rules to `pro_lints/recommended.yaml` and resolved related lint issues. +- **deps**: Updated `pro_lints`, `test`, and `meta` dependencies to latest versions. +- **test**: Refactored pool benchmarks for better accuracy and reliability. +- **docs**: Fixed minor typos and improved documentation for `BinaryWriterPool`. +- **docs**: Complete README overhaul with a focus on recipes and technical clarity. + ## 3.0.0 **Improvements:** diff --git a/README.md b/README.md index c019d46..7714254 100644 --- a/README.md +++ b/README.md @@ -4,559 +4,131 @@ [![Tests](https://github.com/pro100andrey/pro_binary/workflows/Tests/badge.svg)](https://github.com/pro100andrey/pro_binary/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -High-performance binary serialization library for Dart with zero-copy operations, efficient memory management, and Protocol Buffers-compatible VarInt encoding. +**High-performance binary serialization for Dart.** Optimized for speed, zero-copy reads, and Protocol Buffers-compatible encoding. -## Features +## ๐Ÿš€ Key Features -- ๐Ÿš€ **Zero-copy reads**: Direct `Uint8List` views without data duplication -- โšก **Optimized writes**: Exponential buffer growth strategy (ร—1.5) with pooling support -- ๐Ÿ”ข **VarInt encoding**: Protocol Buffers-compatible variable-length integer encoding -- ๐ŸŽฏ **Type-safe API**: Full support for all Dart primitive types (int8-int64, float32/64, bool) -- ๐ŸŒ **Endianness support**: Both big-endian (default) and little-endian byte order -- ๐Ÿ“ฆ **Memory efficient**: Automatic buffer management with configurable initial capacity -- ๐Ÿงช **Battle-tested**: 556+ tests with extensive edge case coverage +* โšก **Zero-Copy Reads**: Operations return `Uint8List` views without allocation. +* ๐Ÿš€ **One-Pass Strings**: Optimized `writeVarString` with optimistic shift (30% faster). +* ๐Ÿ“ฆ **Smart Buffering**: Exponential growth (ร—1.5) and object pooling. +* ๐Ÿ”ข **Compact Encoding**: VarInt & ZigZag support (Protobuf compatible). +* ๐ŸŒ **Universal**: Supports Native & Web (WASM/JS) with consistent API. +* ๐ŸŽฏ **Modern API**: Leverages Dart Extension Types for zero-overhead abstractions. -## Installation +## ๐Ÿ“ฆ Installation ```yaml dependencies: - pro_binary: ^3.0.0 + pro_binary: ^3.0.1 ``` -## Quick Start +## โšก Quick Start ```dart import 'package:pro_binary/pro_binary.dart'; -// Writing data +// Serialize final writer = BinaryWriter(); -writer.writeUint32(42); -writer.writeVarString('Hello, World!'); // Length-prefixed +writer + ..writeUint32(42) + ..writeVarString('Dart ๐Ÿš€') + ..writeBool(true); final bytes = writer.takeBytes(); -// Reading data +// Deserialize final reader = BinaryReader(bytes); -final number = reader.readUint32(); // 42 -final text = reader.readVarString(); // 'Hello, World!' +print(reader.readUint32()); // 42 +print(reader.readVarString()); // Dart ๐Ÿš€ +print(reader.readBool()); // true ``` -## Core API - -### Writing Data - -```dart -final writer = BinaryWriter(); - -// Integers (8, 16, 32, 64-bit signed/unsigned) -writer.writeUint8(255); -writer.writeInt32(-1000, .little); -writer.writeUint64(9999999); - -// Floats -writer.writeFloat32(3.14); -writer.writeFloat64(3.14159265359); - -// Variable-length integers (space-efficient!) -writer.writeVarUint(42); // Unsigned VarInt -writer.writeVarInt(-42); // Signed VarInt with ZigZag - -// Binary data -writer.writeBytes([1, 2, 3]); // Raw bytes -writer.writeVarBytes([1, 2, 3]); // Length-prefixed bytes - -// Strings -writer.writeString('text'); // Raw UTF-8 string (no length prefix) -writer.writeVarString('Hello'); // Length-prefixed UTF-8 string - -// Boolean -writer.writeBool(true); // Single byte (0x01 or 0x00) - -// Get result -final bytes = writer.takeBytes(); // Gets bytes and resets -// or -final view = writer.toBytes(); // Gets bytes, keeps state -``` - -### Reading Data - -```dart -final reader = BinaryReader(bytes); - -// Read primitives (matching write order) -final u8 = reader.readUint8(); -final i32 = reader.readInt32(.little); -final f64 = reader.readFloat64(); - -// Variable-length integers -final count = reader.readVarUint(); -final delta = reader.readVarInt(); - -// Binary data -final data = reader.readBytes(10); // Read 10 bytes -final varData = reader.readVarBytes(); // Read length-prefixed bytes -final remaining = reader.readRemainingBytes(); // Read all remaining - -// Strings -final text = reader.readString(10); // Read 10 UTF-8 bytes -final message = reader.readVarString(); // Read length-prefixed string - -// Boolean -final flag = reader.readBool(); // Read boolean (0x00 = false, other = true) - -// Navigation -reader.skip(4); // Skip bytes -reader.seek(10); // Jump to position 10 -reader.rewind(2); // Move back 2 bytes -final peek = reader.peekBytes(2); // Look ahead without consuming -reader.reset(); // Go back to start - -// Check state -print(reader.offset); // Current position -print(reader.length); // Total buffer size -print(reader.availableBytes); // Bytes left to read -if (reader.hasBytes(4)) { // Check if enough bytes available - final value = reader.readUint32(); -} -``` - -## Real-World Examples - -### Protocol Messages +## ๐Ÿ› ๏ธ Recipes & Patterns +### 1. Efficient Object Serialization ```dart -// Encode message -final writer = BinaryWriter(); -writer.writeUint8(0x42); // Message type -writer.writeVarBytes(payload); // Length-prefixed payload -sendToServer(writer.takeBytes()); - -// Decode message -final reader = BinaryReader(received); -final type = reader.readUint8(); -final payload = reader.readVarBytes(); // Reads length + data -``` - -### Length-Prefixed Strings - -```dart -// Recommended: Use writeVarString (automatic length) -writer.writeVarString('Hello, ไธ–็•Œ! ๐ŸŒ'); - -// Or manually (equivalent to above): -final text = 'Hello, ไธ–็•Œ! ๐ŸŒ'; -final utf8Length = getUtf8Length(text); // Calculate UTF-8 byte length -writer.writeVarUint(utf8Length); -writer.writeString(text); - -// Reading: Use readVarString (reads length + data) -final text = reader.readVarString(); - -// Or manually (equivalent to above): -final length = reader.readVarUint(); -final text = reader.readString(length); -``` - -### Struct-like Data - -```dart -class Player { +class User { final int id; final String name; - final double x, y; - Player(this.id, this.name, this.x, this.y); + User(this.id, this.name); - void writeTo(BinaryWriter w) { - w.writeUint32(id); - w.writeVarString(name); // Length-prefixed string - w.writeFloat64(x); - w.writeFloat64(y); - } + void encode(BinaryWriter w) => w..writeVarUint(id)..writeVarString(name); - static Player readFrom(BinaryReader r) { - final id = r.readUint32(); - final name = r.readVarString(); // Reads length + string - final x = r.readFloat64(); - final y = r.readFloat64(); - return Player(id, name, x, y); - } + factory User.decode(BinaryReader r) => User(r.readVarUint(), r.readVarString()); } ``` -## Architecture - -### BinaryWriter +### 2. High-Frequency writes (Pooling) +Avoid GC pressure by reusing writer instances. +**Recommended (Safe & Concise):** ```dart -final writer = BinaryWriter(initialBufferSize: 128); // Default: 128 bytes +final data = BinaryWriterPool.withWriter((writer) { + writer.writeUint32(1); + writer.writeVarString('Dart Rocks!'); + return writer.toBytes(); // View of the buffer +}); ``` -**Buffer Management:** - -- Initial capacity: 128 bytes (configurable) -- Growth strategy: `newCapacity = ((currentCapacity * 1.5).ceil() + 63) & ~63` (1.5ร— + 64-byte alignment) -- Minimum expansion: Ensures space for requested bytes -- Resets buffer without reallocation: `writer.reset()` -- Takes ownership of buffer: `writer.takeBytes()` (one-time use, resets writer) -- Creates view without reset: `writer.toBytes()` (reusable) - -**Write Operations:** - -- Fixed-width integers: `writeUint8`, `writeInt16`, `writeUint32`, `writeInt64`, etc. -- Variable-length integers: `writeVarUint` (unsigned), `writeVarInt` (ZigZag-encoded signed) -- Floating-point: `writeFloat32`, `writeFloat64` -- Binary data: `writeBytes`, `writeVarBytes` (length-prefixed) -- Strings: `writeString` (raw UTF-8), `writeVarString` (length-prefixed) -- Boolean: `writeBool` (1 byte: 0x00 or 0x01) - -**Helper Functions:** - -- `getUtf8Length(String)`: Calculate UTF-8 byte length without encoding - -### BinaryReader - +**Low-level API:** ```dart -final reader = BinaryReader(bytes); -``` - -**Zero-Copy Design:** - -- No buffer copying: operates on `Uint8List.view` of input data -- Direct memory access via `ByteData` for endianness handling -- Automatic offset tracking with bounds checking - -**Read Operations:** - -- Fixed-width integers: `readUint8`, `readInt16`, `readUint32`, `readInt64`, etc. -- Variable-length integers: `readVarUint`, `readVarInt` (ZigZag-decoded) -- Floating-point: `readFloat32`, `readFloat64` -- Binary data: `readBytes`, `readVarBytes`, `readRemainingBytes` -- Strings: `readString` (raw UTF-8), `readVarString` (length-prefixed) -- Boolean: `readBool` - -**Navigation API:** - -- `skip(int bytes)`: Move forward by N bytes -- `seek(int position)`: Jump to absolute position -- `rewind(int bytes)`: Move backward by N bytes -- `reset()`: Return to start -- `peekBytes(int length, [int offset])`: Look ahead without consuming -- `hasBytes(int count)`: Check if enough bytes available - -**State Inspection:** - -- `offset`: Current read position (0-based) -- `length`: Total buffer size -- `availableBytes`: Remaining unread bytes - -## VarInt Encoding - -VarInt uses fewer bytes for small numbers: - -```dart -writer.writeVarUint(42); // 1 byte (vs 4 for Uint32) -writer.writeVarUint(300); // 2 bytes -writer.writeVarUint(1000000); // 3 bytes - -writer.writeVarInt(-1); // 1 byte (ZigZag encoded) -writer.writeVarInt(-1000); // 2 bytes -``` - -**Implementation Details:** - -- Protocol Buffers Base 128 Varint encoding -- 7 data bits + 1 continuation bit per byte -- Maximum 10 bytes for 64-bit values -- ZigZag encoding for signed integers: `(n << 1) ^ (n >> 63)` -- Fast path optimization for single-byte values (0-127) - -**Use VarUint** for: lengths, counts, IDs -**Use VarInt** for: deltas, offsets, signed values - -## Encoding Efficiency - -VarInt encoding significantly reduces payload size for small values: - -| Value | VarInt | Fixed Uint32 | Savings | -| ------- | -------- | -------------- | --------- | -| 0 | 1 byte | 4 bytes | **75%** | -| 42 | 1 byte | 4 bytes | **75%** | -| 127 | 1 byte | 4 bytes | **75%** | -| 128 | 2 bytes | 4 bytes | **50%** | -| 300 | 2 bytes | 4 bytes | **50%** | -| 16,384 | 3 bytes | 4 bytes | **25%** | -| 1,000,000 | 3 bytes | 4 bytes | **25%** | -| 268,435,455 | 4 bytes | 4 bytes | **0%** | - -**Use VarInt for:** lengths, counts, sizes, small IDs -**Use fixed-width for:** timestamps, coordinates, fixed-size IDs - -## Tips & Best Practices - -### Performance Optimization - -**Pre-allocate buffers** for known data sizes: - -```dart -// For ~1KB messages -final writer = BinaryWriter(initialBufferSize: 1024); - -// Avoid multiple small allocations -final writer = BinaryWriter(initialBufferSize: 8192); // For bulk writes -``` - -**Use object pooling** for high-frequency operations: - -```dart -// Acquire from pool (default 1KB buffer) final writer = BinaryWriterPool.acquire(); try { - writer.writeUint32(value); - final bytes = writer.toBytes(); // Get bytes, keep writer alive - send(bytes); + writer.writeUint32(1); + writer.writeVarString('Dart Rocks!'); + final data = writer.toBytes(); } finally { - // Return to pool for reuse (max 32 writers, max 64KB buffers) BinaryWriterPool.release(writer); } - -// Pool statistics -final stats = BinaryWriterPool.stats; -print('Pooled writers: ${stats.pooled}'); // Current pool size -print('Max pool size: ${stats.maxPoolSize}'); // Maximum capacity (32) -print('Peak pool size: ${stats.peakPoolSize}'); // High water mark -print('Acquire hits: ${stats.acquireHit}'); // Successful reuses -print('Acquire misses: ${stats.acquireMiss}'); // New allocations -print('Hit rate: ${(stats.hitRate * 100).toStringAsFixed(1)}%'); // Cache efficiency -print('Discarded: ${stats.discardedLargeBuffers}'); // Oversized buffers - -// Clear pool manually -BinaryWriterPool.clear(); -``` - -**Choose correct integer type**: - -```dart -// VarInt for small values (lengths, counts) -writer.writeVarUint(items.length); // 1 byte for length < 128 - -// Fixed-width for large/unpredictable values -writer.writeUint32(timestamp); // Always 4 bytes, predictable -writer.writeUint64(uuid); // Fixed 8 bytes -``` - -### Endianness - -Default: **big-endian** (network byte order). Specify when needed: - -```dart -// Explicit endianness -writer.writeUint32(value, .little); -writer.writeFloat64(3.14, .big); - -// Reading must match writing -final value = reader.readUint32(.little); -``` - -**When to use little-endian:** - -- Interop with x86/ARM systems (native byte order) -- Matching existing binary formats (e.g., RIFF, BMP) -- Performance-critical code on little-endian CPUs - -### String Encoding - -Always use **length-prefixed** encoding for variable-length strings: - -```dart -// โœ… Good: Self-describing -writer.writeVarString('Hello'); -// Equivalent to: -// writer.writeVarUint(utf8.encode('Hello').length); -// writer.writeString('Hello'); - -// โŒ Bad: No way to determine string boundaries -writer.writeString('Hello'); -writer.writeString('World'); // Where does first string end? -``` - -For **fixed-length** strings, calculate UTF-8 byte length: - -```dart -final text = 'Hello, ไธ–็•Œ!'; -final utf8Length = getUtf8Length(text); // Calculate without encoding -writer.writeUint16(utf8Length); // Store byte length -writer.writeString(text); - -// Reading -final byteLength = reader.readUint16(); -final text = reader.readString(byteLength); - -// Handle malformed UTF-8 sequences -final strict = reader.readString(10, allowMalformed: false); // Throws on error -final lenient = reader.readString(10); // allowMalformed: true (default) - uses ๏ฟฝ ``` -### Error Handling - -All operations throw `RangeError` on invalid data or buffer overflow: - +### 3. Binary Packets (Manual navigation) ```dart -// Buffer underflow -try { - final value = reader.readUint32(); // Not enough bytes -} on RangeError catch (e) { - print('Buffer underflow: $e'); -} - -// Invalid VarInt -try { - final value = reader.readVarInt(); // Malformed encoding -} on FormatException catch (e) { - print('Invalid VarInt: $e'); -} - -// String decoding errors -try { - final text = reader.readString(10, allowMalformed: false); -} on FormatException catch (e) { - print('Invalid UTF-8: $e'); +final reader = BinaryReader(bytes); +final type = reader[0]; // Absolute peek via operator [] +reader.skip(1); +if (reader.hasBytes(4)) { + final payload = reader(4); // Concise call syntax for readBytes } ``` -### Design Patterns +## ๐Ÿ”ข VarInt Efficiency -**Tagged unions** (discriminated unions): +VarInt encoding reduces payload size by up to **75%** for small values: -```dart -enum MessageType { ping, data, ack } +| Value | VarInt Size | Fixed Uint32 | Savings | +| :------------------ | :---------- | :----------- | :------ | +| `0..127` | 1 byte | 4 bytes | **75%** | +| `128..16,383` | 2 bytes | 4 bytes | **50%** | +| `16,384..2,097,151` | 3 bytes | 4 bytes | **25%** | -void writeMessage(BinaryWriter w, MessageType type, dynamic payload) { - w.writeUint8(type.index); - switch (type) { - case MessageType.ping: - // No payload - break; - case MessageType.data: - w.writeVarBytes(payload as List); // Length-prefixed - break; - case MessageType.ack: - w.writeUint32(payload as int); // Sequence number - break; - } -} +*Use `writeVarUint` for lengths/counts and `writeVarInt` (ZigZag) for signed deltas.* -void readMessage(BinaryReader r) { - final type = MessageType.values[r.readUint8()]; - switch (type) { - case MessageType.ping: - // No payload - break; - case MessageType.data: - final payload = r.readVarBytes(); // Reads length + data - break; - case MessageType.ack: - final seqNum = r.readUint32(); - break; - } -} -``` +## ๐Ÿ“š API Reference Summary -**Version-tolerant serialization**: +### **BinaryWriter** +* **Fixed:** `writeUint8`, `writeInt16`, `writeUint32`, `writeInt64`, `writeFloat64`, `writeBool`. +* **Variable:** `writeVarUint` (unsigned), `writeVarInt` (signed). +* **Data:** `writeBytes`, `writeVarBytes`, `writeString`, `writeVarString`. +* **Management:** `takeBytes()` (reset), `toBytes()` (view), `reset()`. -```dart -class Message { - static const int version = 2; - - void writeTo(BinaryWriter w) { - w.writeUint8(version); // Version byte - w.writeVarUint(id); // Field 1 - w.writeVarString(text); // Field 2 - // Version 2: added timestamp - if (version >= 2) { - w.writeUint64(timestamp); - } - } - - static Message readFrom(BinaryReader r) { - final ver = r.readUint8(); - final id = r.readVarUint(); - final text = r.readVarString(); - final timestamp = ver >= 2 ? r.readUint64() : 0; - return Message(id, text, timestamp); - } -} -``` - -## Testing - -Comprehensive test suite with **556 tests** covering: - -- โœ… **Unit tests (417)**: Isolated BinaryReader/Writer method testing - - All primitive types (int8-int64, float32/64, bool) - - VarInt/VarUint encoding/decoding (70+ dedicated tests) - - Boundary conditions and overflow detection - - UTF-8 handling (multi-byte chars, emojis, malformed sequences) - - Navigation API (seek, skip, rewind, peek) - - Error handling and exception cases - -- โœ… **Integration tests (92)**: End-to-end roundtrip validation - - Write โ†’ Read consistency for all data types - - Buffer expansion under load - - Complex data structure serialization +### **BinaryReader** +* **Fixed:** `readUint8`, `readInt16`, `readUint32`, `readInt64`, `readFloat64`, `readBool`. +* **Variable:** `readVarUint`, `readVarInt`. +* **Data:** `readBytes`, `readVarBytes`, `readString`, `readVarString`, `readRemainingBytes`. +* **Navigation:** `skip(n)`, `seek(p)`, `rewind(n)`, `peekBytes(n)`, `[index]`. -- โœ… **Performance benchmarks (51)**: Optimization tracking - - Read/write throughput for all operations - - Buffer growth patterns - - VarInt encoding efficiency by value range - - Navigation operation costs +## ๐Ÿงช Testing & Performance -Run tests: +We maintain a rigorous test suite: +* โœ… **Native (JIT/AOT)**: Optimized for raw performance. +* โœ… **Web (WASM/JS)**: Cross-platform consistency. +Run benchmarks to see it in action: ```bash -# Run unit + integration tests (skip benchmarks) -dart test -x benchmark - -# Run performance benchmarks only -dart test -t benchmark - -# Run all tests including benchmarks -dart test - -# Run specific test file -dart test test/unit/binary_reader_test.dart - -# Run with coverage -dart pub global activate coverage -dart pub global run coverage:test_with_coverage -- -x benchmark - -# Code analysis -dart analyze --fatal-infos -dart format --set-exit-if-changed . +dart run benchmark_harness:bench --flavor aot --target test/performance/serialization_bench.dart ``` -## Contributing - -Contributions are welcome! Please: - -1. **Open an issue** first to discuss major changes -2. **Follow existing code style** (run `dart format`) -3. **Add tests** for new features (maintain >95% coverage) -4. **Update documentation** including README examples -5. **Run full test suite** before submitting PR - - ```bash - dart analyze --fatal-infos - dart format --set-exit-if-changed . - dart test - ``` - -See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines. - -## License - -MIT License - see [LICENSE](./LICENSE) for details. - ---- +## ๐Ÿ“„ License -Need help? Found a bug? Have a feature request? -๐Ÿ‘‰ [Open an issue](https://github.com/pro100andrey/pro_binary/issues) +MIT License. See [LICENSE](LICENSE) for details. diff --git a/analysis_options.yaml b/analysis_options.yaml index a51e948..4c2d792 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,3 @@ -include: package:pro_lints/common.yaml - -formatter: - trailing_commas: preserve +include: package:pro_lints/recommended.yaml diff --git a/benchmark_baseline.json b/benchmark_baseline.json deleted file mode 100644 index 27de111..0000000 --- a/benchmark_baseline.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "schema": 1, - "createdAt": "2025-12-29T13:09:01.837273Z", - "dart": "Dart SDK version: 3.10.4 (stable) (Tue Dec 9 00:01:55 2025 -0800) on \"linux_x64\"", - "runs": 10, - "warmup": 2, - "tag": "benchmark", - "benchmarkRegex": null, - "mediansUs": { - "BinaryReader performance test": 3706.0389090909093, - "BinaryWriter performance test": 150.3888255564516, - "GetStringLength performance test": 6697.887155444721 - } -} \ No newline at end of file diff --git a/lib/src/binary_reader.dart b/lib/src/binary_reader.dart index 0d884a7..e076dfe 100644 --- a/lib/src/binary_reader.dart +++ b/lib/src/binary_reader.dart @@ -90,26 +90,44 @@ extension type const BinaryReader._(_ReaderState _rs) { return byte; } - // Multi-byte VarInt (optimized for 2-3 byte case) + // Multi-byte VarInt (Optimized: unrolled first 3 bytes) var result = byte & 0x7f; - var shift = 7; - // Process remaining bytes: up to 9 more (total 10 max) - for (var i = 1; i < 10; i++) { + // 2nd byte + if (offset >= len) { + throw RangeError('VarInt out of bounds (truncated)'); + } + byte = list[offset++]; + result |= (byte & 0x7f) << 7; + if ((byte & 0x80) == 0) { + _rs.offset = offset; + return result; + } + + // 3rd byte + if (offset >= len) { + throw RangeError('VarInt out of bounds (truncated)'); + } + byte = list[offset++]; + result |= (byte & 0x7f) << 14; + if ((byte & 0x80) == 0) { + _rs.offset = offset; + return result; + } + + // Fallback loop for remaining bytes (up to 10 total) + var shift = 21; + for (var i = 3; i < 10; i++) { if (offset >= len) { - throw RangeError( - 'VarInt out of bounds: offset=$offset length=$len (truncated)', - ); + throw RangeError('VarInt out of bounds (truncated)'); } byte = list[offset++]; - result |= (byte & 0x7f) << shift; if ((byte & 0x80) == 0) { _rs.offset = offset; return result; } - shift += 7; } @@ -637,6 +655,30 @@ extension type const BinaryReader._(_ReaderState _rs) { _rs.offset = 0; } + /// Returns the byte at the specified absolute [index] in the buffer. + /// + /// This allows random access without affecting the current [offset]. + /// + /// Example: + /// ```dart + /// final firstByte = reader[0]; + /// ``` + @pragma('vm:prefer-inline') + @pragma('dart2js:tryInline') + int operator [](int index) => _rs.list[index]; + + /// Reads [length] bytes from the current position. + /// + /// This is a concise alias for [readBytes]. + /// + /// Example: + /// ```dart + /// final data = reader(10); // Same as reader.readBytes(10) + /// ``` + @pragma('vm:prefer-inline') + @pragma('dart2js:tryInline') + Uint8List call(int length) => readBytes(length); + /// Internal method to check if enough bytes are available to read. /// /// Throws an assertion error in debug mode if not enough bytes. diff --git a/lib/src/binary_writer.dart b/lib/src/binary_writer.dart index fd62bcc..fa2564d 100644 --- a/lib/src/binary_writer.dart +++ b/lib/src/binary_writer.dart @@ -582,9 +582,75 @@ extension type BinaryWriter._(_WriterState _ws) { @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void writeVarString(String value, {bool allowMalformed = true}) { - final utf8Length = getUtf8Length(value); - writeVarUint(utf8Length); + final len = value.length; + if (len == 0) { + writeVarUint(0); + return; + } + + // Step 1: Optimistic estimation of VarInt size based on string length. + // Most strings are ASCII, where byte length == character length. + int estimatedVarIntSize; + if (len < 0x80) { + estimatedVarIntSize = 1; + } else if (len < 0x4000) { + estimatedVarIntSize = 2; + } else if (len < 0x200000) { + estimatedVarIntSize = 3; + } else if (len < 0x10000000) { + estimatedVarIntSize = 4; + } else { + estimatedVarIntSize = 5; + } + + // Ensure enough space for the worst-case scenario (3 bytes per UTF-16 unit) + _ws.ensureSize(estimatedVarIntSize + len * 3); + final startOffset = _ws.offset; + + // Step 2: Skip space for the estimated VarInt length + _ws.offset += estimatedVarIntSize; + + // Step 3: Write the actual string data writeString(value, allowMalformed: allowMalformed); + + final byteLength = _ws.offset - (startOffset + estimatedVarIntSize); + + // Step 4: Check if our estimate was correct for the actual byte length + int actualVarIntSize; + if (byteLength < 0x80) { + actualVarIntSize = 1; + } else if (byteLength < 0x4000) { + actualVarIntSize = 2; + } else if (byteLength < 0x200000) { + actualVarIntSize = 3; + } else if (byteLength < 0x10000000) { + actualVarIntSize = 4; + } else { + actualVarIntSize = 5; + } + + // Step 5: If the estimate was wrong, shift the string data + if (actualVarIntSize != estimatedVarIntSize) { + final shift = actualVarIntSize - estimatedVarIntSize; + if (shift > 0) { + _ws.ensureSize(shift); + } + + // Fast native memory shift using setRange (memmove) + _ws.list.setRange( + startOffset + actualVarIntSize, + _ws.offset + shift, + _ws.list, + startOffset + estimatedVarIntSize, + ); + _ws.offset += shift; + } + + // Step 6: Backtrack and write the actual VarInt length + final finalOffset = _ws.offset; + _ws.offset = startOffset; + writeVarUint(byteLength); + _ws.offset = finalOffset; } /// Writes a boolean value as a single byte. @@ -605,6 +671,18 @@ extension type BinaryWriter._(_WriterState _ws) { writeUint8(value ? 1 : 0); } + /// Writes a sequence of bytes. + /// + /// This is a concise alias for [writeBytes]. + /// + /// Example: + /// ```dart + /// writer([1, 2, 3]); // Same as writer.writeBytes([1, 2, 3]) + /// ``` + @pragma('vm:prefer-inline') + @pragma('dart2js:tryInline') + void call(List bytes) => writeBytes(bytes); + /// Extracts all written bytes and resets the writer. /// /// After calling this method, the writer is reset and ready for reuse. @@ -738,57 +816,53 @@ final class _WriterState { @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void ensureSize(int size) { - if (offset + size <= capacity) { - return; + assert(!_isInPool, 'Cannot ensure size on a pooled writer'); + if (offset + size > capacity) { + _expand(size); } - - _expand(size); } @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void ensureOneByte() { - if (offset + 1 <= capacity) { - return; + assert(!_isInPool, 'Cannot ensure size on a pooled writer'); + if (offset + 1 > capacity) { + _expand(1); } - - _expand(1); } @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void ensureTwoBytes() { - if (offset + 2 <= capacity) { - return; + assert(!_isInPool, 'Cannot ensure size on a pooled writer'); + if (offset + 2 > capacity) { + _expand(2); } - - _expand(2); } @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void ensureFourBytes() { - if (offset + 4 <= capacity) { - return; + assert(!_isInPool, 'Cannot ensure size on a pooled writer'); + if (offset + 4 > capacity) { + _expand(4); } - - _expand(4); } @pragma('vm:prefer-inline') @pragma('dart2js:tryInline') void ensureEightBytes() { - if (offset + 8 <= capacity) { - return; + assert(!_isInPool, 'Cannot ensure size on a pooled writer'); + if (offset + 8 > capacity) { + _expand(8); } - - _expand(8); } /// Expands the buffer to accommodate additional data. /// /// Uses exponential growth (1.5x) for better memory efficiency, /// but ensures the buffer is always large enough for the requested size. + @pragma('vm:never-inline') void _expand(int size) { final req = offset + size; // Grow by 1.5x (exponential growth with better memory efficiency) @@ -903,7 +977,6 @@ int getUtf8Length(String value) { } // Disable lint to allow static-only class for pooling -// ignore: avoid_classes_with_only_static_members /// Object pool for reusing [BinaryWriter] instances to reduce GC pressure. /// /// This pool maintains a cache of [BinaryWriter] instances with their @@ -952,6 +1025,7 @@ int getUtf8Length(String value) { /// - Use [clear] to free pooled memory explicitly /// /// See also: [BinaryWriter], [stats] for pool monitoring +// ignore: avoid_classes_with_only_static_members abstract final class BinaryWriterPool { // The internal pool of reusable writer states. static final _pool = <_WriterState>[]; @@ -992,7 +1066,7 @@ abstract final class BinaryWriterPool { /// final writer = BinaryWriterPool.acquire(); /// try { /// writer.writeUint32(123); - /// return writer.toBytes(); + /// return writer.takeBytes(); /// } finally { /// BinaryWriterPool.release(writer); /// } @@ -1003,13 +1077,44 @@ abstract final class BinaryWriterPool { if (_pool.isNotEmpty) { _acquireHit++; final state = _pool.removeLast().._isInPool = false; + + if (state.capacity < defaultBufferSize) { + state.ensureSize(defaultBufferSize); + } + return BinaryWriter._(state); } _acquireMiss++; + return BinaryWriter(initialBufferSize: defaultBufferSize); } + /// Acquires a writer, executes the given [action], and automatically + /// releases the writer back to the pool. + /// + /// This is the recommended way to use the pool as it ensures the writer + /// is always released even if an exception occurs. + /// + /// Example: + /// ```dart + /// final bytes = BinaryWriterPool.withWriter((writer) { + /// writer.writeUint32(42); + /// return writer.takeBytes(); + /// }); + /// ``` + static T withWriter( + T Function(BinaryWriter writer) action, [ + int defaultBufferSize = _defaultBufferSize, + ]) { + final writer = acquire(defaultBufferSize); + try { + return action(writer); + } finally { + release(writer); + } + } + /// Returns a [BinaryWriter] to the pool for future reuse. /// /// The writer is reset (offset cleared) and stored for future [acquire] @@ -1028,7 +1133,7 @@ abstract final class BinaryWriterPool { /// writer.writeUint32(42); /// final bytes = writer.toBytes(); /// BinaryWriterPool.release(writer); - /// // DON'T USE: writer.writeString('invalid'); + /// // DON'T USE writer here - it's returned to the pool and may be reused! /// ``` /// /// Parameters: @@ -1103,6 +1208,12 @@ abstract final class BinaryWriterPool { /// BinaryWriterPool.clear(); // All pooled writers discarded /// ``` static void clear() { + // Assist GC by breaking links to potentially large byte buffers + for (var i = 0; i < _pool.length; i++) { + _pool[i] + ..list = Uint8List(0) + ..data = ByteData(0); + } _pool.clear(); _acquireHit = 0; _acquireMiss = 0; diff --git a/pubspec.yaml b/pubspec.yaml index dd29268..c85300d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: pro_binary description: Efficient binary serialization library for Dart. Encodes and decodes various data types. -version: 3.0.0 +version: 3.1.0 repository: https://github.com/pro100andrey/pro_binary issue_tracker: https://github.com/pro100andrey/pro_binary/issues @@ -26,8 +26,8 @@ environment: dev_dependencies: benchmark_harness: ^2.4.0 - pro_lints: ^5.1.0 - test: ^1.28.0 + pro_lints: ^6.0.0 + test: ^1.31.1 dependencies: - meta: ^1.17.0 + meta: ^1.18.2 diff --git a/test/performance/deserialization_bench.dart b/test/performance/deserialization_bench.dart new file mode 100644 index 0000000..03102c6 --- /dev/null +++ b/test/performance/deserialization_bench.dart @@ -0,0 +1,138 @@ +import 'dart:typed_data'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:pro_binary/pro_binary.dart'; + +/// Benchmark for reading a simple message. +class SimpleMessageReadBenchmark extends BenchmarkBase { + SimpleMessageReadBenchmark() : super('Read: Simple Message'); + + late BinaryReader reader; + late Uint8List buffer; + var _checksum = 0; + + @override + void setup() { + final writer = BinaryWriter() + ..writeUint32(123456789) + ..writeFloat32(3.14159) + ..writeBool(true) + ..writeInt16(-100) + ..writeUint8(255); + buffer = writer.takeBytes(); + reader = BinaryReader(buffer); + } + + @override + void run() { + reader.reset(); + _checksum += reader.readUint32(); + _checksum += reader.readFloat32().toInt(); + _checksum += reader.readBool() ? 1 : 0; + _checksum += reader.readInt16(); + _checksum += reader.readUint8(); + } + + @override + void teardown() { + if (_checksum == 0) { + // + // ignore: avoid_print + print('Prevent DCE'); + } + } +} + +/// Benchmark for reading a complex profile. +class ComplexProfileReadBenchmark extends BenchmarkBase { + ComplexProfileReadBenchmark() : super('Read: Complex Profile'); + + late BinaryReader reader; + late Uint8List buffer; + var _checksum = 0; + + @override + void setup() { + final writer = BinaryWriter() + ..writeVarUint(1001) + ..writeVarString('John Alexander Doe') + ..writeVarString('john.doe.very.long.email.address@example.com') + ..writeBool(false); + + final scores = List.generate(20, (i) => i * 100); + writer.writeVarUint(scores.length); + for (final score in scores) { + writer.writeVarInt(score); + } + buffer = writer.takeBytes(); + reader = BinaryReader(buffer); + } + + @override + void run() { + reader.reset(); + _checksum += reader.readVarUint(); + _checksum += reader.readVarString().length; + _checksum += reader.readVarString().length; + _checksum += reader.readBool() ? 1 : 0; + + final count = reader.readVarUint(); + for (var i = 0; i < count; i++) { + _checksum += reader.readVarInt(); + } + } + + @override + void teardown() { + if (_checksum == 0) { + // + // ignore: avoid_print + print('Prevent DCE'); + } + } +} + +/// Benchmark for reading large arrays of data. +class LargeArrayReadBenchmark extends BenchmarkBase { + LargeArrayReadBenchmark() : super('Read: Large Array (10K ints)'); + + late BinaryReader reader; + late Uint8List buffer; + var _checksum = 0; + + @override + void setup() { + final writer = BinaryWriter(); + final data = List.generate(10000, (i) => i); + writer.writeVarUint(data.length); + for (final val in data) { + writer.writeUint32(val); + } + buffer = writer.takeBytes(); + reader = BinaryReader(buffer); + } + + @override + void run() { + reader.reset(); + final count = reader.readVarUint(); + for (var i = 0; i < count; i++) { + _checksum += reader.readUint32(); + } + } + + @override + void teardown() { + if (_checksum == 0) { + // + // ignore: avoid_print + print('Prevent DCE'); + } + } +} + +void main() { + SimpleMessageReadBenchmark().report(); + ComplexProfileReadBenchmark().report(); + LargeArrayReadBenchmark().report(); +} diff --git a/test/performance/pool_bench.dart b/test/performance/pool_bench.dart new file mode 100644 index 0000000..9a6d520 --- /dev/null +++ b/test/performance/pool_bench.dart @@ -0,0 +1,47 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:pro_binary/pro_binary.dart'; + +/// Benchmark for the full acquire-use-release cycle of the BinaryWriterPool. +class PoolFullCycleBenchmark extends BenchmarkBase { + PoolFullCycleBenchmark() : super('Pool: Full Cycle (Acquire-Write-Release)'); + + @override + void setup() { + BinaryWriterPool.clear(); + // Warm up the pool + for (var i = 0; i < 10; i++) { + final w = BinaryWriterPool.acquire()..writeUint32(i); + BinaryWriterPool.release(w); + } + } + + @override + void run() { + final writer = BinaryWriterPool.acquire() + ..writeUint32(42) + ..writeVarString('Pooled writer usage'); + + // We use toBytes() instead of takeBytes() to keep the buffer for reuse + final _ = writer.toBytes(); + + BinaryWriterPool.release(writer); + } +} + +/// Benchmark comparing pooled vs non-pooled (raw allocation) performance. +class RawAllocationBenchmark extends BenchmarkBase { + RawAllocationBenchmark() : super('Pool: Raw Allocation (No Pool)'); + + @override + void run() { + final writer = BinaryWriter(initialBufferSize: 1024) + ..writeUint32(42) + ..writeVarString('Pooled writer usage'); + final _ = writer.toBytes(); + } +} + +void main() { + PoolFullCycleBenchmark().report(); + RawAllocationBenchmark().report(); +} diff --git a/test/performance/reader/binary_read_bench_test.dart b/test/performance/reader/binary_read_bench_test.dart deleted file mode 100644 index 61c3924..0000000 --- a/test/performance/reader/binary_read_bench_test.dart +++ /dev/null @@ -1,506 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for reading small byte arrays (< 16 bytes) -/// -/// Small reads are common for fixed-size headers, checksums, and IDs. -class SmallBytesReadBenchmark extends BenchmarkBase { - SmallBytesReadBenchmark() : super('Bytes read: small (8 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); - - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readBytes(8); - } - reader.reset(); - } -} - -/// Benchmark for reading medium byte arrays (64 bytes) -class MediumBytesReadBenchmark extends BenchmarkBase { - MediumBytesReadBenchmark() : super('Bytes read: medium (64 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(64, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readBytes(64); - } - reader.reset(); - } -} - -/// Benchmark for reading large byte arrays (1 KB) -class LargeBytesReadBenchmark extends BenchmarkBase { - LargeBytesReadBenchmark() : super('Bytes read: large (1 KB)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(initialBufferSize: 1024 * 1024); - final data = Uint8List.fromList(List.generate(1024, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readBytes(1024); - } - reader.reset(); - } -} - -/// Benchmark for reading very large byte arrays (64 KB) -class VeryLargeBytesReadBenchmark extends BenchmarkBase { - VeryLargeBytesReadBenchmark() : super('Bytes read: very large (64 KB)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(64 * 1024, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 10; i++) { - reader.readBytes(64 * 1024); - } - reader.reset(); - } -} - -/// Benchmark for reading VarBytes (length-prefixed byte arrays) -class VarBytesSmallReadBenchmark extends BenchmarkBase { - VarBytesSmallReadBenchmark() : super('VarBytes read: small'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); - - for (var i = 0; i < 1000; i++) { - writer.writeVarBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarBytes(); - } - reader.reset(); - } -} - -/// Benchmark for reading VarBytes with medium-sized data -class VarBytesMediumReadBenchmark extends BenchmarkBase { - VarBytesMediumReadBenchmark() : super('VarBytes read: medium'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(256, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeVarBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarBytes(); - } - reader.reset(); - } -} - -/// Benchmark for reading VarBytes with large data -class VarBytesLargeReadBenchmark extends BenchmarkBase { - VarBytesLargeReadBenchmark() : super('VarBytes read: large'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(4096, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeVarBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarBytes(); - } - reader.reset(); - } -} - -/// Benchmark for reading empty byte arrays -class EmptyBytesReadBenchmark extends BenchmarkBase { - EmptyBytesReadBenchmark() : super('Bytes read: empty'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 1000; i++) { - writer.writeBytes([]); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readBytes(0); - } - reader.reset(); - } -} - -/// Benchmark for peeking at bytes without advancing position -class PeekBytesReadBenchmark extends BenchmarkBase { - PeekBytesReadBenchmark() : super('Bytes peek: 16 bytes'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(16, (i) => i)); - - writer.writeBytes(data); - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.peekBytes(16); - } - // No reset needed - we're not advancing position - } -} - -/// Benchmark for reading remaining bytes -class ReadRemainingBytesReadBenchmark extends BenchmarkBase { - ReadRemainingBytesReadBenchmark() : super('readRemainingBytes'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(1024, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readBytes(1024); - } - reader.reset(); - } -} - -/// Benchmark for mixed-size byte reads (realistic scenario) -/// -/// Simulates reading a protocol with headers, payloads, and checksums. -class MixedBytesReadBenchmark extends BenchmarkBase { - MixedBytesReadBenchmark() : super('Bytes read: mixed sizes (realistic)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - // Simulate a protocol message: - // - Header (16 bytes) - // - Payload (variable: 64, 128, 256 bytes) - // - Checksum (4 bytes) - for (var i = 0; i < 1000; i++) { - final header = Uint8List.fromList(List.generate(16, (j) => j)); - final payload = Uint8List.fromList( - List.generate(64 + (i % 3) * 64, (j) => (j + i) % 256), - ); - final checksum = Uint8List.fromList([0xDE, 0xAD, 0xBE, 0xEF]); - - writer - ..writeBytes(header) - ..writeBytes(payload) - ..writeBytes(checksum); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..readBytes(16) // Header - ..readBytes(64 + (i % 3) * 64) // Payload - ..readBytes(4); // Checksum - } - reader.reset(); - } -} - -/// Benchmark for alternating small and large reads -class AlternatingBytesReadBenchmark extends BenchmarkBase { - AlternatingBytesReadBenchmark() : super('Bytes read: alternating sizes'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final small = Uint8List.fromList([1, 2, 3, 4]); - final large = Uint8List.fromList(List.generate(512, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer - ..writeBytes(small) - ..writeBytes(large); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..readBytes(4) - ..readBytes(512); - } - reader.reset(); - } -} - -/// Benchmark for sequential small reads -/// -/// Tests performance when reading many small chunks sequentially. -class SequentialSmallReadsReadBenchmark extends BenchmarkBase { - SequentialSmallReadsReadBenchmark() - : super('Bytes read: sequential small reads'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeUint8(i % 256); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readBytes(1); - } - reader.reset(); - } -} - -/// Benchmark for reading with skip operations -class SkipAndReadBenchmark extends BenchmarkBase { - SkipAndReadBenchmark() : super('Bytes read: skip + read pattern'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - final data = Uint8List.fromList(List.generate(8, (j) => (i + j) % 256)); - final padding = Uint8List.fromList(List.generate(8, (_) => 0)); - writer - ..writeBytes(data) - ..writeBytes(padding); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..readBytes(8) // Read data - ..skip(8); // Skip padding - } - reader.reset(); - } -} - -void main() { - test('Fixed-size reads benchmarks:', () { - EmptyBytesReadBenchmark().report(); - SmallBytesReadBenchmark().report(); - MediumBytesReadBenchmark().report(); - LargeBytesReadBenchmark().report(); - VeryLargeBytesReadBenchmark().report(); - }, tags: ['benchmark']); - - test('VarBytes (length-prefixed) benchmarks:', () { - VarBytesSmallReadBenchmark().report(); - VarBytesMediumReadBenchmark().report(); - VarBytesLargeReadBenchmark().report(); - }, tags: ['benchmark']); - - test('Special operations benchmarks:', () { - PeekBytesReadBenchmark().report(); - ReadRemainingBytesReadBenchmark().report(); - }, tags: ['benchmark']); - - test('Realistic scenarios benchmarks:', () { - MixedBytesReadBenchmark().report(); - AlternatingBytesReadBenchmark().report(); - SequentialSmallReadsReadBenchmark().report(); - SkipAndReadBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/reader/fixed_int_read_bench_test.dart b/test/performance/reader/fixed_int_read_bench_test.dart deleted file mode 100644 index 6838728..0000000 --- a/test/performance/reader/fixed_int_read_bench_test.dart +++ /dev/null @@ -1,530 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for reading Uint8 (1 byte unsigned) -/// -/// Most basic read operation - single byte access without endianness concerns. -/// Should be the fastest fixed-int read operation. -class Uint8ReadBenchmark extends BenchmarkBase { - Uint8ReadBenchmark() : super('Uint8 read'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeUint8(i % 256); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint8(); - } - reader.reset(); - } -} - -/// Benchmark for reading Int8 (1 byte signed) -class Int8ReadBenchmark extends BenchmarkBase { - Int8ReadBenchmark() : super('Int8 read'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeInt8((i % 256) - 128); // Range: -128 to 127 - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt8(); - } - reader.reset(); - } -} - -/// Benchmark for reading Uint16 in big-endian format -class Uint16BigEndianReadBenchmark extends BenchmarkBase { - Uint16BigEndianReadBenchmark() : super('Uint16 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeUint16((i * 257) % 65536); // Varied values - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint16(); - } - reader.reset(); - } -} - -/// Benchmark for reading Uint16 in little-endian format -class Uint16LittleEndianReadBenchmark extends BenchmarkBase { - Uint16LittleEndianReadBenchmark() : super('Uint16 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeUint16((i * 257) % 65536, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint16(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Int16 in big-endian format -class Int16BigEndianReadBenchmark extends BenchmarkBase { - Int16BigEndianReadBenchmark() : super('Int16 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeInt16((i * 257) % 65536 - 32768); // Range: -32768 to 32767 - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt16(); - } - reader.reset(); - } -} - -/// Benchmark for reading Int16 in little-endian format -class Int16LittleEndianReadBenchmark extends BenchmarkBase { - Int16LittleEndianReadBenchmark() : super('Int16 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeInt16((i * 257) % 65536 - 32768, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt16(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Uint32 in big-endian format -class Uint32BigEndianReadBenchmark extends BenchmarkBase { - Uint32BigEndianReadBenchmark() : super('Uint32 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Uint32 values - for (var i = 0; i < 1000; i++) { - writer.writeUint32((i * 1000000 + i * 123) % 4294967296); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint32(); - } - reader.reset(); - } -} - -/// Benchmark for reading Uint32 in little-endian format -class Uint32LittleEndianReadBenchmark extends BenchmarkBase { - Uint32LittleEndianReadBenchmark() : super('Uint32 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeUint32((i * 1000000 + i * 123) % 4294967296, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Int32 in big-endian format -class Int32BigEndianReadBenchmark extends BenchmarkBase { - Int32BigEndianReadBenchmark() : super('Int32 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Int32 values - for (var i = 0; i < 1000; i++) { - writer.writeInt32((i * 1000000 + i * 123) % 4294967296 - 2147483648); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt32(); - } - reader.reset(); - } -} - -/// Benchmark for reading Int32 in little-endian format -class Int32LittleEndianReadBenchmark extends BenchmarkBase { - Int32LittleEndianReadBenchmark() : super('Int32 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeInt32( - (i * 1000000 + i * 123) % 4294967296 - 2147483648, - .little, - ); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Uint64 in big-endian format -class Uint64BigEndianReadBenchmark extends BenchmarkBase { - Uint64BigEndianReadBenchmark() : super('Uint64 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Uint64 values - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i * 1000000000 + i * 12345); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint64(); - } - reader.reset(); - } -} - -/// Benchmark for reading Uint64 in little-endian format -class Uint64LittleEndianReadBenchmark extends BenchmarkBase { - Uint64LittleEndianReadBenchmark() : super('Uint64 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Uint64 values in little-endian - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i * 1000000000 + i * 12345, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readUint64(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Int64 in big-endian format -class Int64BigEndianReadBenchmark extends BenchmarkBase { - Int64BigEndianReadBenchmark() : super('Int64 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Int64 values - for (var i = 0; i < 1000; i++) { - final value = i.isEven - ? (i * 1000000000 + i * 12345) - : -(i * 1000000000 + i * 12345); - writer.writeInt64(value); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt64(); - } - reader.reset(); - } -} - -/// Benchmark for reading Int64 in little-endian format -class Int64LittleEndianReadBenchmark extends BenchmarkBase { - Int64LittleEndianReadBenchmark() : super('Int64 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Int64 values in little-endian - for (var i = 0; i < 1000; i++) { - final value = i.isEven - ? (i * 1000000000 + i * 12345) - : -(i * 1000000000 + i * 12345); - writer.writeInt64(value, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readInt64(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading mixed fixed-width integers (realistic scenario) -/// -/// Simulates real-world protocol where various integer sizes are mixed. -/// Uses little-endian as it's more common in modern protocols. -class MixedFixedIntReadBenchmark extends BenchmarkBase { - MixedFixedIntReadBenchmark() : super('Mixed fixed-int read (realistic)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(initialBufferSize: 8192); - for (var i = 0; i < 1000; i++) { - writer - ..writeUint8(127) // Message type - ..writeUint16(10, .little) // Length - ..writeUint32(1000, .little) // ID - ..writeInt32(-100, .little) // Signed value - ..writeUint64(1000000000, .little) // Timestamp - ..writeInt8(64) // Small signed value - ..writeInt16(-1000, .little) // Medium signed value - ..writeInt64(-10000000, .little); // Large signed value - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..readUint8() - ..readUint16(.little) - ..readUint32(.little) - ..readInt32(.little) - ..readUint64(.little) - ..readInt8() - ..readInt16(.little) - ..readInt64(.little); - } - reader.reset(); - } -} - -void main() { - test('8-bit integer benchmarks:', () { - Uint8ReadBenchmark().report(); - Int8ReadBenchmark().report(); - }, tags: ['benchmark']); - - test('16-bit integer benchmarks:', () { - Uint16BigEndianReadBenchmark().report(); - Uint16LittleEndianReadBenchmark().report(); - Int16BigEndianReadBenchmark().report(); - Int16LittleEndianReadBenchmark().report(); - }, tags: ['benchmark']); - - test('32-bit integer benchmarks:', () { - Uint32BigEndianReadBenchmark().report(); - Uint32LittleEndianReadBenchmark().report(); - Int32BigEndianReadBenchmark().report(); - Int32LittleEndianReadBenchmark().report(); - }, tags: ['benchmark']); - - test('64-bit integer benchmarks:', () { - Uint64BigEndianReadBenchmark().report(); - Uint64LittleEndianReadBenchmark().report(); - Int64BigEndianReadBenchmark().report(); - Int64LittleEndianReadBenchmark().report(); - }, tags: ['benchmark']); - - test('Mixed integer benchmarks:', () { - MixedFixedIntReadBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/reader/float_read_bench_test.dart b/test/performance/reader/float_read_bench_test.dart deleted file mode 100644 index 69f187b..0000000 --- a/test/performance/reader/float_read_bench_test.dart +++ /dev/null @@ -1,466 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for reading Float32 in big-endian format -/// -/// Float32 (IEEE 754 single precision) is commonly used for graphics, -/// game data, and scientific computing where memory efficiency matters. -class Float32BigEndianReadBenchmark extends BenchmarkBase { - Float32BigEndianReadBenchmark() : super('Float32 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 1000; i++) { - final value = (i * 3.14159) - 500.0; - writer.writeFloat32(value); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat32(); - } - reader.reset(); - } -} - -/// Benchmark for reading Float32 in little-endian format -class Float32LittleEndianReadBenchmark extends BenchmarkBase { - Float32LittleEndianReadBenchmark() : super('Float32 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 Float32 values in little-endian - for (var i = 0; i < 1000; i++) { - final value = (i * 3.14159) - 500.0; - writer.writeFloat32(value, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float64 in big-endian format -/// -/// Float64 (IEEE 754 double precision) is the default floating-point type -/// in Dart and most high-level languages. Used for general-purpose math. -class Float64BigEndianReadBenchmark extends BenchmarkBase { - Float64BigEndianReadBenchmark() : super('Float64 read (big-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - final value = (i * 2.718281828) - 1000.0; - writer.writeFloat64(value); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat64(); - } - reader.reset(); - } -} - -/// Benchmark for reading Float64 in little-endian format -class Float64LittleEndianReadBenchmark extends BenchmarkBase { - Float64LittleEndianReadBenchmark() : super('Float64 read (little-endian)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - final value = (i * 2.718281828) - 1000.0; - writer.writeFloat64(value, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat64(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float32 special values (NaN, Infinity) -/// -/// Special IEEE 754 values may have different performance characteristics -/// due to how hardware handles them. -class Float32SpecialValuesReadBenchmark extends BenchmarkBase { - Float32SpecialValuesReadBenchmark() : super('Float32 read (special values)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 200; i++) { - writer - ..writeFloat32(.nan, .little) - ..writeFloat32(.infinity, .little) - ..writeFloat32(.negativeInfinity, .little) - ..writeFloat32(-0, .little) - ..writeFloat32(1, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float64 special values (NaN, Infinity) -class Float64SpecialValuesReadBenchmark extends BenchmarkBase { - Float64SpecialValuesReadBenchmark() : super('Float64 read (special values)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 200; i++) { - writer - ..writeFloat64(.nan, .little) - ..writeFloat64(.infinity, .little) - ..writeFloat64(.negativeInfinity, .little) - ..writeFloat64(-0, .little) - ..writeFloat64(1, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat64(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float32 with small values (subnormal range) -/// -/// Subnormal numbers (very close to zero) may have different performance. -class Float32SmallValuesReadBenchmark extends BenchmarkBase { - Float32SmallValuesReadBenchmark() : super('Float32 read (small values)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(initialBufferSize: 8192); - - for (var i = 0; i < 1000; i++) { - final value = (i + 1) * 1e-38; // Near Float32 min positive normal - writer.writeFloat32(value, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float64 with small values (subnormal range) -class Float64SmallValuesReadBenchmark extends BenchmarkBase { - Float64SmallValuesReadBenchmark() : super('Float64 read (small values)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - final value = (i + 1) * 1e-308; // Near Float64 min positive normal - writer.writeFloat64(value, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat64(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float32 with large values -class Float32LargeValuesReadBenchmark extends BenchmarkBase { - Float32LargeValuesReadBenchmark() : super('Float32 read (large values)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - final value = (i + 1) * 1e35; // Near Float32 max (~3.4e38) - writer.writeFloat32(value, .little); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading Float64 with large values -class Float64LargeValuesReadBenchmark extends BenchmarkBase { - Float64LargeValuesReadBenchmark() : super('Float64 read (large values)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - final value = (i + 1) * 1e305; // Near Float64 max (~1.8e308) - writer.writeFloat64(value, .little); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readFloat64(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading mixed Float32 and Float64 (realistic scenario) -/// -/// Simulates real-world usage where both precision levels are used. -/// For example: positions (Float32) + precise calculations (Float64). -class MixedFloatReadBenchmark extends BenchmarkBase { - MixedFloatReadBenchmark() : super('Mixed float read (realistic)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 100; i++) { - writer - // 3D position (Float32 x3) - ..writeFloat32(i * 1.5, .little) - ..writeFloat32(i * 2.0, .little) - ..writeFloat32(i * 0.5, .little) - // Rotation quaternion (Float32 x4) - ..writeFloat32(0.707, .little) - ..writeFloat32(0, .little) - ..writeFloat32(0.707, .little) - ..writeFloat32(0, .little) - // Precise timestamp (Float64) - ..writeFloat64(i * 1000000.0, .little) - // Color (Float32 x4 - RGBA) - ..writeFloat32(0.5, .little) - ..writeFloat32(0.8, .little) - ..writeFloat32(0.2, .little) - ..writeFloat32(1, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - // Read position - reader - ..readFloat32(.little) - ..readFloat32(.little) - ..readFloat32(.little) - // Read rotation - ..readFloat32(.little) - ..readFloat32(.little) - ..readFloat32(.little) - ..readFloat32(.little) - // Read timestamp - ..readFloat64(.little) - // Read color - ..readFloat32(.little) - ..readFloat32(.little) - ..readFloat32(.little) - ..readFloat32(.little); - } - reader.reset(); - } -} - -/// Benchmark for reading alternating Float32/Float64 -/// -/// Tests performance when switching between 32-bit and 64-bit reads. -class AlternatingFloatReadBenchmark extends BenchmarkBase { - AlternatingFloatReadBenchmark() : super('Alternating Float32/Float64 read'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 500; i++) { - writer - ..writeFloat32(i * 3.14, .little) - ..writeFloat64(i * 2.718, .little); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 500; i++) { - reader - ..readFloat32(.little) - ..readFloat64(.little); - } - reader.reset(); - } -} - -void main() { - test('Float32 benchmarks:', () { - Float32BigEndianReadBenchmark().report(); - Float32LittleEndianReadBenchmark().report(); - Float32SmallValuesReadBenchmark().report(); - Float32LargeValuesReadBenchmark().report(); - Float32SpecialValuesReadBenchmark().report(); - }, tags: ['benchmark']); - - test('Float64 benchmarks:', () { - Float64BigEndianReadBenchmark().report(); - Float64LittleEndianReadBenchmark().report(); - Float64SmallValuesReadBenchmark().report(); - Float64LargeValuesReadBenchmark().report(); - Float64SpecialValuesReadBenchmark().report(); - }, tags: ['benchmark']); - - test('Mixed float benchmarks:', () { - MixedFloatReadBenchmark().report(); - AlternatingFloatReadBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/reader/navigation_bench_test.dart b/test/performance/reader/navigation_bench_test.dart deleted file mode 100644 index 9364117..0000000 --- a/test/performance/reader/navigation_bench_test.dart +++ /dev/null @@ -1,535 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for skip operations (small offsets) -/// -/// Skip is commonly used to jump over padding, unused fields, or known -/// sections. -class SkipSmallOffsetBenchmark extends BenchmarkBase { - SkipSmallOffsetBenchmark() : super('Skip: small offset (8 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.skip(8); - } - reader.reset(); - } -} - -/// Benchmark for skip operations (medium offsets) -class SkipMediumOffsetBenchmark extends BenchmarkBase { - SkipMediumOffsetBenchmark() : super('Skip: medium offset (256 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(256, (i) => i % 256)); - - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.skip(256); - } - reader.reset(); - } -} - -/// Benchmark for skip operations (large offsets) -class SkipLargeOffsetBenchmark extends BenchmarkBase { - SkipLargeOffsetBenchmark() : super('Skip: large offset (4 KB)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(4096, (i) => i % 256)); - // Write 1000 chunks of 4KB - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.skip(4096); - } - reader.reset(); - } -} - -/// Benchmark for seek operations (forward) -/// -/// Seek is used for random access patterns, like jumping to specific offsets. -class SeekForwardBenchmark extends BenchmarkBase { - SeekForwardBenchmark() : super('Seek: forward (sequential positions)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 100KB of data - final data = Uint8List.fromList(List.generate(100000, (i) => i % 256)); - writer.writeBytes(data); - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - // Seek to 1000 different positions - for (var i = 0; i < 1000; i++) { - reader.seek((i * 100) % 90000); - } - reader.reset(); - } -} - -/// Benchmark for seek operations (backward) -class SeekBackwardBenchmark extends BenchmarkBase { - SeekBackwardBenchmark() : super('Seek: backward (reverse positions)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(100000, (i) => i % 256)); - writer.writeBytes(data); - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - - reader.seek(90000); // Start near end - } - - @override - void exercise() => run(); - - @override - void run() { - // Seek backward to 1000 different positions - for (var i = 1000; i > 0; i--) { - reader.seek((i * 90) % 90000); - } - reader.reset(); - } -} - -/// Benchmark for seek operations (random access) -class SeekRandomAccessBenchmark extends BenchmarkBase { - SeekRandomAccessBenchmark() : super('Seek: random access pattern'); - - late BinaryReader reader; - late Uint8List buffer; - late List positions; - - @override - void setup() { - final writer = BinaryWriter(); - final data = Uint8List.fromList(List.generate(100000, (i) => i % 256)); - - writer.writeBytes(data); - buffer = writer.takeBytes(); - - reader = BinaryReader(buffer); - // Pre-calculate random-like positions (deterministic for consistency) - positions = List.generate(1000, (i) => (i * 7919) % 90000); - } - - @override - void exercise() => run(); - - @override - void run() { - // Disable lint for using for-in to emphasize the benchmark nature - // ignore: prefer_foreach - for (final pos in positions) { - reader.seek(pos); - } - reader.reset(); - } -} - -/// Benchmark for rewind operations -/// -/// Rewind resets position to the beginning - common in parsing retry scenarios. -class RewindBenchmark extends BenchmarkBase { - RewindBenchmark() : super('Rewind: reset to start'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..skip(8) - ..reset(); - } - } -} - -/// Benchmark for reset operations -/// -/// Reset is similar to rewind - tests the efficiency of position reset. -class ResetBenchmark extends BenchmarkBase { - ResetBenchmark() : super('Reset: position reset'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..skip(8) - ..reset(); - } - } -} - -/// Benchmark for getPosition operations -/// -/// Getting current position (offset) is often needed in parsing to track -/// offsets. -class GetPositionBenchmark extends BenchmarkBase { - GetPositionBenchmark() : super('offset: query current position'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.skip(8); - } - reader.reset(); - } -} - -/// Benchmark for remainingBytes getter -class RemainingBytesBenchmark extends BenchmarkBase { - RemainingBytesBenchmark() : super('availableBytes: query remaining length'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.skip(8); - } - reader.reset(); - } -} - -/// Benchmark for combined navigation operations (realistic parsing) -/// -/// Simulates a parser that needs to: -/// 1. Check position -/// 2. Peek at header -/// 3. Decide to skip or read -/// 4. Move to next section -class RealisticParsingNavigationBenchmark extends BenchmarkBase { - RealisticParsingNavigationBenchmark() - : super('Navigation: realistic parsing pattern'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write protocol-like data: header (4 bytes) + payload (variable) - for (var i = 0; i < 1000; i++) { - final payloadSize = 16 + (i % 8) * 8; - writer - ..writeUint32(payloadSize) // Header with payload size - ..writeBytes(List.generate(payloadSize, (j) => (i + j) % 256)); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - // 1. Get current position - reader.offset; - // 2. Peek at header to determine payload size - final peekData = reader.peekBytes(4); - final payloadSize = ByteData.view(peekData.buffer).getUint32(0); - // 3. Skip header - reader.skip(4); - // 4. Decide: skip payload based on some condition - if (i % 3 == 0) { - reader.skip(payloadSize); - } else { - // Read and process payload - reader.readBytes(payloadSize); - } - } - reader.reset(); - } -} - -/// Benchmark for seek + read pattern -/// -/// Common in binary file formats with indexes or tables of contents. -class SeekAndReadBenchmark extends BenchmarkBase { - SeekAndReadBenchmark() : super('Navigation: seek + read pattern'); - - late BinaryReader reader; - late Uint8List buffer; - late List offsets; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 100 records of 64 bytes each - offsets = []; - for (var i = 0; i < 100; i++) { - offsets.add(i * 64); // Track offsets manually - final data = Uint8List.fromList(List.generate(64, (j) => (i + j) % 256)); - writer.writeBytes(data); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - // Read records in non-sequential order - for (var i = 0; i < 100; i++) { - final idx = (i * 7) % 100; - reader - ..seek(offsets[idx]) - ..readBytes(64); - } - reader.reset(); - } -} - -/// Benchmark for skip + peek pattern -/// -/// Used when scanning through data looking for specific patterns. -class SkipAndPeekBenchmark extends BenchmarkBase { - SkipAndPeekBenchmark() : super('Navigation: skip + peek pattern'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write pattern: 4 bytes to skip, 4 bytes to peek - for (var i = 0; i < 1000; i++) { - writer - ..writeUint32(0xDEADBEEF) // Skip this - ..writeUint32(i); // Peek at this - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..skip(4) - ..peekBytes(4) - ..skip(4); - } - reader.reset(); - } -} - -/// Benchmark for backward navigation (seek back and re-read) -/// -/// Used when parser needs to backtrack. -class BacktrackNavigationBenchmark extends BenchmarkBase { - BacktrackNavigationBenchmark() : super('Navigation: backtrack pattern'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - for (var i = 0; i < 2000; i++) { - writer.writeUint32(i); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 500; i++) { - // Read forward - reader - ..readUint32() - ..readUint32(); - final pos = reader.offset; - reader - ..readUint32() - // Backtrack to saved position - ..seek(pos) - // Re-read - ..readUint32(); - } - reader.reset(); - } -} - -void main() { - test('Skip operation benchmarks:', () { - SkipSmallOffsetBenchmark().report(); - SkipMediumOffsetBenchmark().report(); - SkipLargeOffsetBenchmark().report(); - }, tags: ['benchmark']); - - test('Seek operation benchmarks:', () { - SeekForwardBenchmark().report(); - SeekBackwardBenchmark().report(); - SeekRandomAccessBenchmark().report(); - }, tags: ['benchmark']); - - test('Position control benchmarks:', () { - RewindBenchmark().report(); - ResetBenchmark().report(); - GetPositionBenchmark().report(); - }, tags: ['benchmark']); - - test('Position query benchmarks:', () { - GetPositionBenchmark().report(); - RemainingBytesBenchmark().report(); - }, tags: ['benchmark']); - - test('Complex navigation patterns:', () { - RealisticParsingNavigationBenchmark().report(); - SeekAndReadBenchmark().report(); - SkipAndPeekBenchmark().report(); - BacktrackNavigationBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/reader/string_read_bench_test.dart b/test/performance/reader/string_read_bench_test.dart deleted file mode 100644 index e54ee4a..0000000 --- a/test/performance/reader/string_read_bench_test.dart +++ /dev/null @@ -1,522 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for reading ASCII strings (fast path) -/// -/// ASCII-only strings use the fast path in UTF-8 decoding, -/// processing multiple bytes at once. This is the most common case. -class AsciiStringReadBenchmark extends BenchmarkBase { - AsciiStringReadBenchmark() : super('String read: ASCII only'); - - late BinaryReader reader; - late Uint8List buffer; - late int stringLength; - - @override - void setup() { - final writer = BinaryWriter(); - const asciiString = 'Hello, World! This is a test string 123456789'; - stringLength = asciiString.length; - - for (var i = 0; i < 1000; i++) { - writer.writeString(asciiString); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(stringLength); - } - reader.reset(); - } -} - -/// Benchmark for reading short ASCII strings (< 16 chars) -class ShortAsciiStringReadBenchmark extends BenchmarkBase { - ShortAsciiStringReadBenchmark() : super('String read: short ASCII'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - const strings = [ - 'Hi', - 'Test', - 'Hello', - 'OK', - 'Error', - 'Success', - '123', - 'ABC', - ]; - - // Write 1000 short strings - for (var i = 0; i < 1000; i++) { - strings.forEach(writer.writeString); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void run() { - // Read in same pattern - for (var i = 0; i < 1000; i++) { - reader - ..readString(2) // Hi - ..readString(4) // Test - ..readString(5) // Hello - ..readString(2) // OK - ..readString(5) // Error - ..readString(7) // Success - ..readString(3) // 123 - ..readString(3); // ABC - } - reader.reset(); - } -} - -/// Benchmark for reading long ASCII strings (> 100 chars) -class LongAsciiStringReadBenchmark extends BenchmarkBase { - LongAsciiStringReadBenchmark() : super('String read: long ASCII'); - - late BinaryReader reader; - late Uint8List buffer; - late int stringLength; - - @override - void setup() { - final writer = BinaryWriter(); - const longString = - 'The quick brown fox jumps over the lazy dog. ' - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.'; - stringLength = longString.length; - - // Write 1000 long ASCII strings - for (var i = 0; i < 1000; i++) { - writer.writeString(longString); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(stringLength); - } - reader.reset(); - } -} - -/// Benchmark for reading Cyrillic strings (2-byte UTF-8) -class CyrillicStringReadBenchmark extends BenchmarkBase { - CyrillicStringReadBenchmark() : super('String read: Cyrillic (2-byte UTF-8)'); - - late BinaryReader reader; - late Uint8List buffer; - late int byteLength; - - @override - void setup() { - final writer = BinaryWriter(); - const cyrillicString = 'ะŸั€ะธะฒะตั‚ ะผะธั€! ะญั‚ะพ ั‚ะตัั‚ะพะฒะฐั ัั‚ั€ะพะบะฐ ะฝะฐ ั€ัƒััะบะพะผ ัะทั‹ะบะต.'; - byteLength = getUtf8Length(cyrillicString); - - // Write 1000 Cyrillic strings - for (var i = 0; i < 1000; i++) { - writer.writeString(cyrillicString); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(byteLength); - } - reader.reset(); - } -} - -/// Benchmark for reading CJK strings (3-byte UTF-8) -class CjkStringReadBenchmark extends BenchmarkBase { - CjkStringReadBenchmark() : super('String read: CJK (3-byte UTF-8)'); - - late BinaryReader reader; - late Uint8List buffer; - late int byteLength; - - @override - void setup() { - final writer = BinaryWriter(); - const cjkString = 'ไฝ ๅฅฝไธ–็•Œ๏ผ่ฟ™ๆ˜ฏไธ€ไธชๆต‹่ฏ•ๅญ—็ฌฆไธฒใ€‚ๆ—ฅๆœฌ่ชžใฎใƒ†ใ‚นใƒˆใ‚‚ๅซใพใ‚Œใฆใ„ใพใ™ใ€‚'; - byteLength = getUtf8Length(cjkString); - - // Write 1000 CJK strings - for (var i = 0; i < 1000; i++) { - writer.writeString(cjkString); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(byteLength); - } - reader.reset(); - } -} - -/// Benchmark for reading emoji strings (4-byte UTF-8) -class EmojiStringReadBenchmark extends BenchmarkBase { - EmojiStringReadBenchmark() : super('String read: Emoji (4-byte UTF-8)'); - - late BinaryReader reader; - late Uint8List buffer; - late int byteLength; - - @override - void setup() { - final writer = BinaryWriter(initialBufferSize: 16384); - const emojiString = '๐Ÿš€ ๐ŸŒ ๐ŸŽ‰ ๐Ÿ‘ ๐Ÿ’ป ๐Ÿ”ฅ โšก ๐ŸŽฏ ๐Ÿ† ๐Ÿ’ก ๐ŸŒˆ โœจ ๐ŸŽจ ๐ŸŽญ ๐ŸŽช'; - byteLength = getUtf8Length(emojiString); - - // Write 1000 emoji strings - for (var i = 0; i < 1000; i++) { - writer.writeString(emojiString); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(byteLength); - } - reader.reset(); - } -} - -/// Benchmark for reading mixed Unicode strings -/// -/// Real-world strings often contain a mix of ASCII, Latin Extended, -/// Cyrillic, CJK, and emoji characters. -class MixedUnicodeStringReadBenchmark extends BenchmarkBase { - MixedUnicodeStringReadBenchmark() : super('String read: mixed Unicode'); - - late BinaryReader reader; - late Uint8List buffer; - late int byteLength; - - @override - void setup() { - final writer = BinaryWriter(); - const mixedString = 'Hello ะผะธั€ ไธ–็•Œ ๐ŸŒ! Test ั‚ะตัั‚ ๆต‹่ฏ• ๐Ÿš€'; - byteLength = getUtf8Length(mixedString); - - // Write 1000 mixed strings - for (var i = 0; i < 1000; i++) { - writer.writeString(mixedString); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(byteLength); - } - reader.reset(); - } -} - -/// Benchmark for reading VarString (length-prefixed strings) -class VarStringAsciiReadBenchmark extends BenchmarkBase { - VarStringAsciiReadBenchmark() : super('VarString read: ASCII'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - const asciiString = 'Hello, World! This is a test string.'; - - // Write 1000 VarStrings - for (var i = 0; i < 1000; i++) { - writer.writeVarString(asciiString); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarString(); - } - reader.reset(); - } -} - -/// Benchmark for reading VarString with mixed Unicode -class VarStringMixedReadBenchmark extends BenchmarkBase { - VarStringMixedReadBenchmark() : super('VarString read: mixed Unicode'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - const mixedString = 'Hello ะผะธั€ ไธ–็•Œ ๐ŸŒ Test ั‚ะตัั‚ ๆต‹่ฏ• ๐Ÿš€'; - - // Write 1000 VarStrings - for (var i = 0; i < 1000; i++) { - writer.writeVarString(mixedString); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarString(); - } - reader.reset(); - } -} - -/// Benchmark for reading empty strings -class EmptyStringReadBenchmark extends BenchmarkBase { - EmptyStringReadBenchmark() : super('String read: empty strings'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - - // Write 1000 empty strings - for (var i = 0; i < 1000; i++) { - writer.writeString(''); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(0); - } - reader.reset(); - } -} - -/// Benchmark for realistic message protocol with strings -/// -/// Simulates reading a typical JSON-like message structure with -/// multiple string fields of varying types and lengths. -class RealisticMessageReadBenchmark extends BenchmarkBase { - RealisticMessageReadBenchmark() : super('String read: realistic message'); - - late BinaryReader reader; - late Uint8List buffer; - late List fieldLengths; - - @override - void setup() { - final writer = BinaryWriter(); - - // Typical message fields - const fields = [ - 'user', // Field name (ASCII) - 'John Doe', // Value (ASCII) - 'email', // Field name (ASCII) - 'john.doe@example.com', // Value (ASCII) - 'message', // Field name (ASCII) - 'Hello ไธ–็•Œ! ๐ŸŒ', // Value (mixed Unicode) - 'timestamp', // Field name (ASCII) - '2024-12-30T12:00:00Z', // Value (ASCII) - 'locale', // Field name (ASCII) - 'ru-RU', // Value (ASCII) - ]; - - fieldLengths = fields.map(getUtf8Length).toList(); - - // Write 1000 messages - for (var i = 0; i < 1000; i++) { - fields.forEach(writer.writeString); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - fieldLengths.forEach(reader.readString); - } - reader.reset(); - } -} - -/// Benchmark for alternating short and long strings -class AlternatingStringReadBenchmark extends BenchmarkBase { - AlternatingStringReadBenchmark() : super('String read: alternating lengths'); - - late BinaryReader reader; - late Uint8List buffer; - late int shortLength; - late int longLength; - - @override - void setup() { - final writer = BinaryWriter(); - const shortString = 'Hi'; - const longString = - 'This is a much longer string with more content to read and process'; - - shortLength = shortString.length; - longLength = longString.length; - - // Alternate between short and long strings - for (var i = 0; i < 1000; i++) { - writer - ..writeString(shortString) - ..writeString(longString); - } - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader - ..readString(shortLength) - ..readString(longLength); - } - reader.reset(); - } -} - -/// Benchmark for reading very long strings (> 1KB) -class VeryLongStringReadBenchmark extends BenchmarkBase { - VeryLongStringReadBenchmark() : super('String read: very long (>1KB)'); - - late BinaryReader reader; - late Uint8List buffer; - late int stringLength; - - @override - void setup() { - final writer = BinaryWriter(); - // Create a ~2KB string - final longString = 'Lorem ipsum dolor sit amet. ' * 80; - stringLength = longString.length; - - // Write 1000 very long strings - for (var i = 0; i < 1000; i++) { - writer.writeString(longString); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readString(stringLength); - } - reader.reset(); - } -} - -void main() { - test('ASCII string benchmarks:', () { - AsciiStringReadBenchmark().report(); - ShortAsciiStringReadBenchmark().report(); - LongAsciiStringReadBenchmark().report(); - EmptyStringReadBenchmark().report(); - }, tags: ['benchmark']); - - test('UTF-8 multi-byte benchmarks:', () { - CyrillicStringReadBenchmark().report(); - CjkStringReadBenchmark().report(); - EmojiStringReadBenchmark().report(); - MixedUnicodeStringReadBenchmark().report(); - }, tags: ['benchmark']); - - test('VarString benchmarks:', () { - VarStringAsciiReadBenchmark().report(); - VarStringMixedReadBenchmark().report(); - }, tags: ['benchmark']); - - test('Realistic string scenarios:', () { - RealisticMessageReadBenchmark().report(); - AlternatingStringReadBenchmark().report(); - VeryLongStringReadBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/reader/varint_read_bench_test.dart b/test/performance/reader/varint_read_bench_test.dart deleted file mode 100644 index c5a4193..0000000 --- a/test/performance/reader/varint_read_bench_test.dart +++ /dev/null @@ -1,348 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for reading VarUint in fast path (single byte: 0-127) -/// -/// This is the most common case in real-world protocols where small numbers -/// (lengths, counts, small IDs) dominate. The fast path should be highly -/// optimized as it's hit most frequently. -class VarUintFastPathBenchmark extends BenchmarkBase { - VarUintFastPathBenchmark() : super('VarUint read: 0-127 (fast path)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 single-byte VarUints - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(i % 128); // Values 0-127 - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarUint(); - } - - reader.reset(); - } -} - -/// Benchmark for reading 2-byte VarUint (128-16383) -/// -/// Second most common case - covers most typical array lengths, -/// message sizes, and medium-range IDs. -class VarUint2ByteBenchmark extends BenchmarkBase { - VarUint2ByteBenchmark() : super('VarUint read: 128-16383 (2 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 two-byte VarUints - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(128 + (i % 100)); // Values 128-227 - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarUint(); - } - - reader.reset(); - } -} - -/// Benchmark for reading 3-byte VarUint (16384-2097151) -class VarUint3ByteBenchmark extends BenchmarkBase { - VarUint3ByteBenchmark() : super('VarUint read: 16384-2097151 (3 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 three-byte VarUints - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(16384 + (i % 1000) * 100); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarUint(); - } - - reader.reset(); - } -} - -/// Benchmark for reading 4-byte VarUint (2097152-268435455) -class VarUint4ByteBenchmark extends BenchmarkBase { - VarUint4ByteBenchmark() : super('VarUint read: 2097152-268435455 (4 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 four-byte VarUints - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(2097152 + (i % 1000) * 10000); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarUint(); - } - - reader.reset(); - } -} - -/// Benchmark for reading 5-byte VarUint (268435456+) -/// -/// Less common in practice but important for large file sizes, -/// timestamps, or 64-bit IDs. -class VarUint5ByteBenchmark extends BenchmarkBase { - VarUint5ByteBenchmark() : super('VarUint read: 268435456+ (5 bytes)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 five-byte VarUints - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(268435456 + i * 1000000); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarUint(); - } - - reader.reset(); - } -} - -/// Benchmark for reading VarInt with ZigZag encoding (small positive values) -/// -/// ZigZag encoding: 0=>0, 1=>2, 2=>4, etc. -/// Tests decoding performance for positive signed integers. -class VarIntPositiveBenchmark extends BenchmarkBase { - VarIntPositiveBenchmark() : super('VarInt read: positive (ZigZag)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 positive VarInts - for (var i = 0; i < 1000; i++) { - writer.writeVarInt(i % 1000); // Values 0-999 - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarInt(); - } - reader.reset(); - } -} - -/// Benchmark for reading VarInt with ZigZag encoding (small negative values) -/// -/// ZigZag encoding: -1=>1, -2=>3, -3=>5, etc. -/// Tests decoding performance for negative signed integers. -class VarIntNegativeBenchmark extends BenchmarkBase { - VarIntNegativeBenchmark() : super('VarInt read: negative (ZigZag)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 negative VarInts - for (var i = 0; i < 1000; i++) { - writer.writeVarInt(-(i % 1000 + 1)); // Values -1 to -1000 - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final _ = reader.readVarInt(); - } - - reader.reset(); - } -} - -/// Benchmark for reading mixed VarInt values (positive and negative) -/// -/// Realistic scenario where data contains both positive and negative values. -class VarIntMixedBenchmark extends BenchmarkBase { - VarIntMixedBenchmark() : super('VarInt read: mixed positive/negative'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(initialBufferSize: 8192); - // Write 1000 mixed VarInts - for (var i = 0; i < 1000; i++) { - final value = i.isEven ? (i ~/ 2) % 100 : -((i ~/ 2) % 100 + 1); - writer.writeVarInt(value); - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarInt(); - } - - reader.reset(); - } -} - -/// Benchmark for reading mixed sizes VarUint (realistic distribution) -/// -/// Simulates real-world usage where most values are small (1-2 bytes) -/// but occasionally large values appear. -/// Distribution: 70% single-byte, 20% two-byte, 8% three-byte, 2% four-byte+ -class VarUintMixedSizesBenchmark extends BenchmarkBase { - VarUintMixedSizesBenchmark() : super('VarUint read: mixed sizes (realistic)'); - - late BinaryReader reader; - late Uint8List buffer; - - @override - void setup() { - final writer = BinaryWriter(); - // Write 1000 VarUints with realistic distribution - for (var i = 0; i < 1000; i++) { - final mod = i % 100; - if (mod < 70) { - // 70% single byte - writer.writeVarUint(i % 128); - } else if (mod < 90) { - // 20% two bytes - writer.writeVarUint(128 + (i % 1000)); - } else if (mod < 98) { - // 8% three bytes - writer.writeVarUint(16384 + (i % 10000)); - } else { - // 2% four+ bytes - writer.writeVarUint(2097152 + i * 1000); - } - } - - buffer = writer.takeBytes(); - reader = BinaryReader(buffer); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - reader.readVarUint(); - } - - reader.reset(); - } -} - -void main() { - test('VarUint size benchmarks:', () { - VarUintFastPathBenchmark().report(); - VarUint2ByteBenchmark().report(); - VarUint3ByteBenchmark().report(); - VarUint4ByteBenchmark().report(); - VarUint5ByteBenchmark().report(); - }, tags: ['benchmark']); - - test('VarInt (ZigZag) benchmarks:', () { - VarIntPositiveBenchmark().report(); - VarIntNegativeBenchmark().report(); - VarIntMixedBenchmark().report(); - }, tags: ['benchmark']); - - test('Realistic scenarios:', () { - VarUintMixedSizesBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/serialization_bench.dart b/test/performance/serialization_bench.dart new file mode 100644 index 0000000..53598fe --- /dev/null +++ b/test/performance/serialization_bench.dart @@ -0,0 +1,98 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:pro_binary/pro_binary.dart'; + +/// Benchmark for a simple message with fixed-size types. +class SimpleMessageWriteBenchmark extends BenchmarkBase { + SimpleMessageWriteBenchmark() : super('Write: Simple Message'); + + late BinaryWriter writer; + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: 64); + } + + @override + void run() { + writer + ..reset() + ..writeUint32(123456789) + ..writeFloat32(3.14159) + ..writeBool(true) + ..writeInt16(-100) + ..writeUint8(255); + + // Ensure data is "consumed" to prevent DCE + if (writer.bytesWritten == 0) { + throw Exception('DCE'); + } + } +} + +/// Benchmark for a complex profile with strings and collections. +class ComplexProfileWriteBenchmark extends BenchmarkBase { + ComplexProfileWriteBenchmark() : super('Write: Complex Profile'); + + late BinaryWriter writer; + + final nameString = 'John Alexander Doe'; + final email = 'john.doe.very.long.email.address@example.com'; + final List scores = List.generate(20, (i) => i * 100); + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: 512); + } + + @override + void run() { + writer + ..reset() + ..writeVarUint(1001) // ID + ..writeVarString(nameString) + ..writeVarString(email) + ..writeBool(false) // IsPremium + ..writeVarUint(scores.length); + for (final score in scores) { + writer.writeVarInt(score); + } + + if (writer.bytesWritten == 0) { + throw Exception('DCE'); + } + } +} + +/// Benchmark for writing large arrays of data. +class LargeArrayWriteBenchmark extends BenchmarkBase { + LargeArrayWriteBenchmark() : super('Write: Large Array (10K ints)'); + + late BinaryWriter writer; + late List data; + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: 1024 * 64); + data = List.generate(10000, (i) => i); + } + + @override + void run() { + writer + ..reset() + ..writeVarUint(data.length); + for (final val in data) { + writer.writeUint32(val); + } + + if (writer.bytesWritten == 0) { + throw Exception('DCE'); + } + } +} + +void main() { + SimpleMessageWriteBenchmark().report(); + ComplexProfileWriteBenchmark().report(); + LargeArrayWriteBenchmark().report(); +} diff --git a/test/performance/strings_bench.dart b/test/performance/strings_bench.dart new file mode 100644 index 0000000..f63e3b1 --- /dev/null +++ b/test/performance/strings_bench.dart @@ -0,0 +1,130 @@ +import 'dart:convert'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:pro_binary/pro_binary.dart'; + +/// Current optimized approach (One-pass optimistic shift) +class OnePassStringBench extends BenchmarkBase { + OnePassStringBench(String name, this.payload) + : super('String [$name] (One-Pass)'); + + final String payload; + late BinaryWriter writer; + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: payload.length * 3 + 10); + } + + @override + void run() { + writer + ..reset() + ..writeVarString(payload); + + if (writer.bytesWritten == 0) { + throw Exception(); + } + } +} + +/// Old two-pass approach (getUtf8Length + writeString) +class TwoPassStringBench extends BenchmarkBase { + TwoPassStringBench(String name, this.payload) + : super('String [$name] (Two-Pass)'); + final String payload; + late BinaryWriter writer; + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: payload.length * 3 + 10); + } + + void _oldWriteVarString(String value) { + final utf8Length = getUtf8Length(value); + writer + ..writeVarUint(utf8Length) + ..writeString(value); + } + + @override + void run() { + writer.reset(); + _oldWriteVarString(payload); + if (writer.bytesWritten == 0) { + throw Exception(); + } + } +} + +/// Standard Dart approach (utf8.encode + bytes.length) +/// This is the most common "correct" way without pro_binary. +class StandardDartCorrectBench extends BenchmarkBase { + StandardDartCorrectBench(String name, this.payload) + : super('String [$name] (utf8.encode + bytes.length)'); + + final String payload; + late BinaryWriter writer; + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: payload.length * 3 + 10); + } + + @override + void run() { + writer.reset(); + final bytes = utf8.encode(payload); + writer + ..writeVarUint(bytes.length) + ..writeBytes(bytes); + if (writer.bytesWritten == 0) { + throw Exception(); + } + } +} + +/// Naive Dart approach (utf8.encode + string.length) +/// Used as a baseline for "theoretical maximum" speed if length was already +/// known. +class StandardDartNaiveBench extends BenchmarkBase { + StandardDartNaiveBench(String name, this.payload) + : super('String [$name] (utf8.encode + string.length)'); + + final String payload; + late BinaryWriter writer; + + @override + void setup() { + writer = BinaryWriter(initialBufferSize: payload.length * 3 + 10); + } + + @override + void run() { + writer.reset(); + final bytes = utf8.encode(payload); + writer + ..writeVarUint(payload.length) // Using character length + ..writeBytes(bytes); + if (writer.bytesWritten == 0) { + throw Exception(); + } + } +} + +void runComparison(String name, String payload) { + OnePassStringBench(name, payload).report(); + TwoPassStringBench(name, payload).report(); + StandardDartCorrectBench(name, payload).report(); + StandardDartNaiveBench(name, payload).report(); +} + +void main() { + const ascii = 'This is a pure ASCII string for fast path testing.'; + const mixed = 'Mixed content: ะ ัƒััะบะธะน ั‚ะตะบัั‚, ไธ–็•Œ, with some English.'; + final emoji = 'Emoji test: ๐ŸŒ๐Ÿš€๐Ÿ”ฅ' * 5; + + runComparison('ASCII', ascii); + runComparison('Mixed UTF-8', mixed); + runComparison('Emoji/Complex', emoji); +} diff --git a/test/performance/writer/binary_write_bench_test.dart b/test/performance/writer/binary_write_bench_test.dart deleted file mode 100644 index 8f78fe2..0000000 --- a/test/performance/writer/binary_write_bench_test.dart +++ /dev/null @@ -1,371 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for writing small byte arrays (< 16 bytes) -class SmallBytesWriteBenchmark extends BenchmarkBase { - SmallBytesWriteBenchmark() : super('Bytes write: small (8 bytes)'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing medium byte arrays (64 bytes) -class MediumBytesWriteBenchmark extends BenchmarkBase { - MediumBytesWriteBenchmark() : super('Bytes write: medium (64 bytes)'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList(List.generate(64, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing large byte arrays (1 KB) -class LargeBytesWriteBenchmark extends BenchmarkBase { - LargeBytesWriteBenchmark() : super('Bytes write: large (1 KB)'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList(List.generate(1024, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing very large byte arrays (64 KB) -class VeryLargeBytesWriteBenchmark extends BenchmarkBase { - VeryLargeBytesWriteBenchmark() : super('Bytes write: very large (64 KB)'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList(List.generate(64 * 1024, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing VarBytes (length-prefixed byte arrays) -class VarBytesSmallWriteBenchmark extends BenchmarkBase { - VarBytesSmallWriteBenchmark() : super('VarBytes write: small'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing VarBytes with medium-sized data -class VarBytesMediumWriteBenchmark extends BenchmarkBase { - VarBytesMediumWriteBenchmark() : super('VarBytes write: medium'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList(List.generate(256, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing VarBytes with large data -class VarBytesLargeWriteBenchmark extends BenchmarkBase { - VarBytesLargeWriteBenchmark() : super('VarBytes write: large'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList(List.generate(4096, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing empty byte arrays -class EmptyBytesWriteBenchmark extends BenchmarkBase { - EmptyBytesWriteBenchmark() : super('Bytes write: empty'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes([]); - } - writer.reset(); - } -} - -/// Benchmark for mixed-size byte writes (realistic scenario) -class MixedBytesWriteBenchmark extends BenchmarkBase { - MixedBytesWriteBenchmark() : super('Bytes write: mixed sizes (realistic)'); - - late BinaryWriter writer; - late Uint8List header; - late List payloads; - late Uint8List checksum; - - @override - void setup() { - writer = BinaryWriter(); - header = Uint8List.fromList(List.generate(16, (j) => j)); - payloads = [ - Uint8List.fromList(List.generate(64, (j) => j % 256)), - Uint8List.fromList(List.generate(128, (j) => j % 256)), - Uint8List.fromList(List.generate(256, (j) => j % 256)), - ]; - checksum = Uint8List.fromList([0xDE, 0xAD, 0xBE, 0xEF]); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer - ..writeBytes(header) - ..writeBytes(payloads[i % 3]) - ..writeBytes(checksum); - } - writer.reset(); - } -} - -/// Benchmark for alternating small and large writes -class AlternatingBytesWriteBenchmark extends BenchmarkBase { - AlternatingBytesWriteBenchmark() : super('Bytes write: alternating sizes'); - - late BinaryWriter writer; - late Uint8List small; - late Uint8List large; - - @override - void setup() { - writer = BinaryWriter(); - small = Uint8List.fromList([1, 2, 3, 4]); - large = Uint8List.fromList(List.generate(512, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer - ..writeBytes(small) - ..writeBytes(large); - } - writer.reset(); - } -} - -/// Benchmark for sequential small writes -class SequentialSmallWritesBenchmark extends BenchmarkBase { - SequentialSmallWritesBenchmark() - : super('Bytes write: sequential small writes'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint8(i % 256); - } - writer.reset(); - } -} - -/// Benchmark for writing bytes from List of int -class ListIntWriteBenchmark extends BenchmarkBase { - ListIntWriteBenchmark() : super('Bytes write: from List'); - - late BinaryWriter writer; - late List data; - - @override - void setup() { - writer = BinaryWriter(); - data = List.generate(64, (i) => i % 256); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for writing bytes from Uint8List view -class Uint8ListViewWriteBenchmark extends BenchmarkBase { - Uint8ListViewWriteBenchmark() : super('Bytes write: Uint8List view'); - - late BinaryWriter writer; - late Uint8List data; - late Uint8List view; - - @override - void setup() { - writer = BinaryWriter(); - data = Uint8List.fromList(List.generate(128, (i) => i % 256)); - view = Uint8List.view(data.buffer, 32, 64); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeBytes(view); - } - writer.reset(); - } -} - -void main() { - test('Fixed-size writes benchmarks:', () { - EmptyBytesWriteBenchmark().report(); - SmallBytesWriteBenchmark().report(); - MediumBytesWriteBenchmark().report(); - LargeBytesWriteBenchmark().report(); - VeryLargeBytesWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('VarBytes (length-prefixed) benchmarks:', () { - VarBytesSmallWriteBenchmark().report(); - VarBytesMediumWriteBenchmark().report(); - VarBytesLargeWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('Realistic scenarios benchmarks:', () { - MixedBytesWriteBenchmark().report(); - AlternatingBytesWriteBenchmark().report(); - SequentialSmallWritesBenchmark().report(); - }, tags: ['benchmark']); - - test('Special input types benchmarks:', () { - ListIntWriteBenchmark().report(); - Uint8ListViewWriteBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/writer/buffer_growth_bench_test.dart b/test/performance/writer/buffer_growth_bench_test.dart deleted file mode 100644 index 9b791b7..0000000 --- a/test/performance/writer/buffer_growth_bench_test.dart +++ /dev/null @@ -1,356 +0,0 @@ -import 'dart:typed_data'; - -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for buffer growth from small initial size -class BufferGrowthSmallInitialBenchmark extends BenchmarkBase { - BufferGrowthSmallInitialBenchmark() - : super('Buffer growth: small initial (16 bytes -> 1KB)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16); - } - - @override - void exercise() => run(); - - @override - void run() { - // Write 1KB of data, forcing multiple expansions - for (var i = 0; i < 256; i++) { - writer.writeUint32(i); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth from medium initial size -class BufferGrowthMediumInitialBenchmark extends BenchmarkBase { - BufferGrowthMediumInitialBenchmark() - : super('Buffer growth: medium initial (256 bytes -> 64KB)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 256); - } - - @override - void exercise() => run(); - - @override - void run() { - // Write 64KB of data - final data = Uint8List.fromList(List.generate(256, (i) => i % 256)); - for (var i = 0; i < 256; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth with incremental writes -class BufferGrowthIncrementalBenchmark extends BenchmarkBase { - BufferGrowthIncrementalBenchmark() - : super('Buffer growth: incremental writes'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 64); - } - - @override - void exercise() => run(); - - @override - void run() { - // Write progressively larger chunks - for (var size = 1; size <= 256; size *= 2) { - final data = Uint8List.fromList(List.generate(size, (i) => i % 256)); - for (var i = 0; i < 10; i++) { - writer.writeBytes(data); - } - } - writer.reset(); - } -} - -/// Benchmark for buffer growth with large single write -class BufferGrowthLargeSingleWriteBenchmark extends BenchmarkBase { - BufferGrowthLargeSingleWriteBenchmark() - : super('Buffer growth: large single write'); - - late BinaryWriter writer; - late Uint8List largeData; - - @override - void setup() { - writer = BinaryWriter(); - largeData = Uint8List.fromList(List.generate(32768, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - // Single large write that forces expansion - writer - ..writeBytes(largeData) - ..reset(); - } -} - -/// Benchmark for buffer growth with string writes -class BufferGrowthStringWritesBenchmark extends BenchmarkBase { - BufferGrowthStringWritesBenchmark() : super('Buffer growth: string writes'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32); - } - - @override - void exercise() => run(); - - @override - void run() { - const testString = 'Hello World! This is a test string.'; - for (var i = 0; i < 500; i++) { - writer.writeString(testString); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth with VarInt writes -class BufferGrowthVarIntWritesBenchmark extends BenchmarkBase { - BufferGrowthVarIntWritesBenchmark() : super('Buffer growth: VarInt writes'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 1024); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 250; i++) { - writer.writeVarUint(i & 0x7F); // Keep values to 0-127 (single byte) - } - writer.reset(); - } -} - -/// Benchmark for buffer growth with mixed writes -class BufferGrowthMixedWritesBenchmark extends BenchmarkBase { - BufferGrowthMixedWritesBenchmark() : super('Buffer growth: mixed data types'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 64); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 200; i++) { - writer - ..writeUint8(i % 256) - ..writeUint32(i * 1000, .little) - ..writeFloat64(i * 3.14, .little) - ..writeString('Message $i') - ..writeVarUint(i); - } - writer.reset(); - } -} - -/// Benchmark for no buffer growth (sufficient initial size) -class NoBufferGrowthBenchmark extends BenchmarkBase { - NoBufferGrowthBenchmark() - : super('No buffer growth: sufficient initial size'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - // Write 32KB without triggering growth - final data = Uint8List.fromList(List.generate(256, (i) => i % 256)); - for (var i = 0; i < 128; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth with VarBytes -class BufferGrowthVarBytesBenchmark extends BenchmarkBase { - BufferGrowthVarBytesBenchmark() : super('Buffer growth: VarBytes writes'); - - late BinaryWriter writer; - late Uint8List data; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16 * 1024); - data = Uint8List.fromList(List.generate(32, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeVarBytes(data); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth pattern: write, reset, write larger -class BufferGrowthResetPatternBenchmark extends BenchmarkBase { - BufferGrowthResetPatternBenchmark() - : super('Buffer growth: write-reset-write pattern'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(); - } - - @override - void exercise() => run(); - - @override - void run() { - // First write: small - for (var i = 0; i < 16; i++) { - writer.writeUint32(i); - } - writer.reset(); - - // Second write: medium (may reuse buffer) - for (var i = 0; i < 64; i++) { - writer.writeUint32(i); - } - writer.reset(); - - // Third write: large (may grow buffer) - for (var i = 0; i < 256; i++) { - writer.writeUint32(i); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth with alternating sizes -class BufferGrowthAlternatingSizesBenchmark extends BenchmarkBase { - BufferGrowthAlternatingSizesBenchmark() - : super('Buffer growth: alternating write sizes'); - - late BinaryWriter writer; - late Uint8List smallData; - late Uint8List largeData; - - @override - void setup() { - writer = BinaryWriter(); - smallData = Uint8List.fromList(List.generate(8, (i) => i)); - largeData = Uint8List.fromList(List.generate(512, (i) => i % 256)); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 50; i++) { - writer - ..writeBytes(smallData) - ..writeBytes(largeData) - ..writeBytes(smallData); - } - writer.reset(); - } -} - -/// Benchmark for buffer growth reaching max reusable capacity -class BufferGrowthMaxCapacityBenchmark extends BenchmarkBase { - BufferGrowthMaxCapacityBenchmark() - : super('Buffer growth: reaching max capacity (64KB)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 1024); - } - - @override - void exercise() => run(); - - @override - void run() { - // Write exactly 64KB to test max reusable capacity - final data = Uint8List.fromList(List.generate(1024, (i) => i % 256)); - for (var i = 0; i < 64; i++) { - writer.writeBytes(data); - } - writer.reset(); - } -} - -void main() { - test('Initial size variations:', () { - BufferGrowthSmallInitialBenchmark().report(); - BufferGrowthMediumInitialBenchmark().report(); - NoBufferGrowthBenchmark().report(); - }, tags: ['benchmark']); - - test('Growth patterns:', () { - BufferGrowthIncrementalBenchmark().report(); - BufferGrowthLargeSingleWriteBenchmark().report(); - BufferGrowthAlternatingSizesBenchmark().report(); - }, tags: ['benchmark']); - - test('Data type specific growth:', () { - BufferGrowthStringWritesBenchmark().report(); - BufferGrowthVarIntWritesBenchmark().report(); - BufferGrowthVarBytesBenchmark().report(); - BufferGrowthMixedWritesBenchmark().report(); - }, tags: ['benchmark']); - - test('Reset and capacity patterns:', () { - BufferGrowthResetPatternBenchmark().report(); - BufferGrowthMaxCapacityBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/writer/fixed_int_write_bench_test.dart b/test/performance/writer/fixed_int_write_bench_test.dart deleted file mode 100644 index c43156d..0000000 --- a/test/performance/writer/fixed_int_write_bench_test.dart +++ /dev/null @@ -1,388 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for writing Uint8 -class Uint8WriteBenchmark extends BenchmarkBase { - Uint8WriteBenchmark() : super('Uint8 write'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint8(i % 256); - } - writer.reset(); - } -} - -/// Benchmark for writing Int8 -class Int8WriteBenchmark extends BenchmarkBase { - Int8WriteBenchmark() : super('Int8 write'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt8((i % 256) - 128); - } - writer.reset(); - } -} - -/// Benchmark for writing Uint16 big-endian -class Uint16BigEndianWriteBenchmark extends BenchmarkBase { - Uint16BigEndianWriteBenchmark() : super('Uint16 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint16(i % 65536); - } - writer.reset(); - } -} - -/// Benchmark for writing Uint16 little-endian -class Uint16LittleEndianWriteBenchmark extends BenchmarkBase { - Uint16LittleEndianWriteBenchmark() : super('Uint16 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint16(i % 65536, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Int16 big-endian -class Int16BigEndianWriteBenchmark extends BenchmarkBase { - Int16BigEndianWriteBenchmark() : super('Int16 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt16((i % 65536) - 32768); - } - writer.reset(); - } -} - -/// Benchmark for writing Int16 little-endian -class Int16LittleEndianWriteBenchmark extends BenchmarkBase { - Int16LittleEndianWriteBenchmark() : super('Int16 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt16((i % 65536) - 32768, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Uint32 big-endian -class Uint32BigEndianWriteBenchmark extends BenchmarkBase { - Uint32BigEndianWriteBenchmark() : super('Uint32 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint32(i * 1000); - } - writer.reset(); - } -} - -/// Benchmark for writing Uint32 little-endian -class Uint32LittleEndianWriteBenchmark extends BenchmarkBase { - Uint32LittleEndianWriteBenchmark() : super('Uint32 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint32(i * 1000, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Int32 big-endian -class Int32BigEndianWriteBenchmark extends BenchmarkBase { - Int32BigEndianWriteBenchmark() : super('Int32 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt32(i * 1000 - 500000); - } - writer.reset(); - } -} - -/// Benchmark for writing Int32 little-endian -class Int32LittleEndianWriteBenchmark extends BenchmarkBase { - Int32LittleEndianWriteBenchmark() : super('Int32 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt32(i * 1000 - 500000, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Uint64 big-endian -class Uint64BigEndianWriteBenchmark extends BenchmarkBase { - Uint64BigEndianWriteBenchmark() : super('Uint64 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i * 1000000); - } - writer.reset(); - } -} - -/// Benchmark for writing Uint64 little-endian -class Uint64LittleEndianWriteBenchmark extends BenchmarkBase { - Uint64LittleEndianWriteBenchmark() : super('Uint64 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeUint64(i * 1000000, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Int64 big-endian -class Int64BigEndianWriteBenchmark extends BenchmarkBase { - Int64BigEndianWriteBenchmark() : super('Int64 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt64(i * 1000000 - 500000000); - } - writer.reset(); - } -} - -/// Benchmark for writing Int64 little-endian -class Int64LittleEndianWriteBenchmark extends BenchmarkBase { - Int64LittleEndianWriteBenchmark() : super('Int64 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeInt64(i * 1000000 - 500000000, .little); - } - writer.reset(); - } -} - -/// Benchmark for mixed fixed-int writes (realistic scenario) -class MixedFixedIntWriteBenchmark extends BenchmarkBase { - MixedFixedIntWriteBenchmark() : super('Mixed fixed-int write (realistic)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer - ..writeUint8(i % 256) - ..writeUint16(i % 65536, .little) - ..writeUint32(i * 1000, .little) - ..writeInt32(i * 100 - 5000, .little) - ..writeUint64(i * 1000000, .little) - ..writeInt8((i % 256) - 128) - ..writeInt16((i % 32768) - 16384, .little) - ..writeInt64(i * 1000000, .little); - } - writer.reset(); - } -} - -void main() { - test('8-bit integer benchmarks:', () { - Uint8WriteBenchmark().report(); - Int8WriteBenchmark().report(); - }, tags: ['benchmark']); - - test('16-bit integer benchmarks:', () { - Uint16BigEndianWriteBenchmark().report(); - Uint16LittleEndianWriteBenchmark().report(); - Int16BigEndianWriteBenchmark().report(); - Int16LittleEndianWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('32-bit integer benchmarks:', () { - Uint32BigEndianWriteBenchmark().report(); - Uint32LittleEndianWriteBenchmark().report(); - Int32BigEndianWriteBenchmark().report(); - Int32LittleEndianWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('64-bit integer benchmarks:', () { - Uint64BigEndianWriteBenchmark().report(); - Uint64LittleEndianWriteBenchmark().report(); - Int64BigEndianWriteBenchmark().report(); - Int64LittleEndianWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('Mixed integer benchmarks:', () { - MixedFixedIntWriteBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/writer/float_write_bench_test.dart b/test/performance/writer/float_write_bench_test.dart deleted file mode 100644 index 4ed162e..0000000 --- a/test/performance/writer/float_write_bench_test.dart +++ /dev/null @@ -1,333 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for writing Float32 big-endian -class Float32BigEndianWriteBenchmark extends BenchmarkBase { - Float32BigEndianWriteBenchmark() : super('Float32 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat32((i * 3.14159) - 500.0); - } - writer.reset(); - } -} - -/// Benchmark for writing Float32 little-endian -class Float32LittleEndianWriteBenchmark extends BenchmarkBase { - Float32LittleEndianWriteBenchmark() : super('Float32 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat32((i * 3.14159) - 500.0, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Float32 small values -class Float32SmallValuesWriteBenchmark extends BenchmarkBase { - Float32SmallValuesWriteBenchmark() : super('Float32 write (small values)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat32((i % 100) * 0.01, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Float32 large values -class Float32LargeValuesWriteBenchmark extends BenchmarkBase { - Float32LargeValuesWriteBenchmark() : super('Float32 write (large values)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat32((i * 1000000.0) - 500000000.0, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Float32 special values -class Float32SpecialValuesWriteBenchmark extends BenchmarkBase { - Float32SpecialValuesWriteBenchmark() - : super('Float32 write (special values)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 250; i++) { - writer - ..writeFloat32(0, .little) - ..writeFloat32(.nan, .little) - ..writeFloat32(.infinity, .little) - ..writeFloat32(.negativeInfinity, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Float64 big-endian -class Float64BigEndianWriteBenchmark extends BenchmarkBase { - Float64BigEndianWriteBenchmark() : super('Float64 write (big-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat64((i * 2.718281828) - 1000.0); - } - writer.reset(); - } -} - -/// Benchmark for writing Float64 little-endian -class Float64LittleEndianWriteBenchmark extends BenchmarkBase { - Float64LittleEndianWriteBenchmark() : super('Float64 write (little-endian)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat64((i * 2.718281828) - 1000.0, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Float64 small values -class Float64SmallValuesWriteBenchmark extends BenchmarkBase { - Float64SmallValuesWriteBenchmark() : super('Float64 write (small values)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat64((i % 100) * 0.001, .little); - } - writer.reset(); - } -} - -/// Benchmark for writing Float64 large values -class Float64LargeValuesWriteBenchmark extends BenchmarkBase { - Float64LargeValuesWriteBenchmark() : super('Float64 write (large values)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeFloat64( - (i * 1000000000.0) - 500000000000.0, - .little, - ); - } - writer.reset(); - } -} - -/// Benchmark for writing Float64 special values -class Float64SpecialValuesWriteBenchmark extends BenchmarkBase { - Float64SpecialValuesWriteBenchmark() - : super('Float64 write (special values)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 250; i++) { - writer - ..writeFloat64(0, .little) - ..writeFloat64(.nan, .little) - ..writeFloat64(.infinity, .little) - ..writeFloat64(.negativeInfinity, .little); - } - writer.reset(); - } -} - -/// Benchmark for mixed float writes (realistic scenario) -class MixedFloatWriteBenchmark extends BenchmarkBase { - MixedFloatWriteBenchmark() : super('Mixed float write (realistic)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer - // Position (3D coordinates) - ..writeFloat32(i * 10.0, .little) - ..writeFloat32(i * 20.0, .little) - ..writeFloat32(i * 30.0, .little) - // Rotation (quaternion) - ..writeFloat32(0, .little) - ..writeFloat32(0, .little) - ..writeFloat32(0, .little) - ..writeFloat32(1, .little) - // Timestamp - ..writeFloat64(i * 0.016, .little) - // Color (RGBA) - ..writeFloat32(1, .little) - ..writeFloat32(0.5, .little) - ..writeFloat32(0, .little) - ..writeFloat32(1, .little); - } - writer.reset(); - } -} - -/// Benchmark for alternating Float32/Float64 -class AlternatingFloatWriteBenchmark extends BenchmarkBase { - AlternatingFloatWriteBenchmark() : super('Alternating Float32/Float64 write'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 500; i++) { - writer - ..writeFloat32(i * 3.14, .little) - ..writeFloat64(i * 2.718, .little); - } - writer.reset(); - } -} - -void main() { - test('Float32 benchmarks:', () { - Float32BigEndianWriteBenchmark().report(); - Float32LittleEndianWriteBenchmark().report(); - Float32SmallValuesWriteBenchmark().report(); - Float32LargeValuesWriteBenchmark().report(); - Float32SpecialValuesWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('Float64 benchmarks:', () { - Float64BigEndianWriteBenchmark().report(); - Float64LittleEndianWriteBenchmark().report(); - Float64SmallValuesWriteBenchmark().report(); - Float64LargeValuesWriteBenchmark().report(); - Float64SpecialValuesWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('Mixed float benchmarks:', () { - MixedFloatWriteBenchmark().report(); - AlternatingFloatWriteBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/writer/pool_bench_test.dart b/test/performance/writer/pool_bench_test.dart deleted file mode 100644 index e56bd48..0000000 --- a/test/performance/writer/pool_bench_test.dart +++ /dev/null @@ -1,332 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for acquiring writers from pool (empty pool) -/// -/// Tests the performance of getting a new writer from the pool. -class PoolAcquireNewBenchmark extends BenchmarkBase { - PoolAcquireNewBenchmark() : super('Pool: acquire new writer'); - - @override - void setup() { - BinaryWriterPool.clear(); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - final writer = BinaryWriterPool.acquire(); - BinaryWriterPool.release(writer); - } - } -} - -/// Benchmark for acquiring reused writers from pool -/// -/// Tests the performance when writers are reused from the pool. -class PoolAcquireReusedBenchmark extends BenchmarkBase { - PoolAcquireReusedBenchmark() : super('Pool: acquire reused writer'); - - late List writers; - - @override - void setup() { - BinaryWriterPool.clear(); - writers = []; - // Pre-fill pool with released writers - for (var i = 0; i < 10; i++) { - final writer = BinaryWriterPool.acquire() - ..writeBytes(List.generate(100, (j) => j % 256)); - writers.add(writer); - } - writers.forEach(BinaryWriterPool.release); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - final writer = BinaryWriterPool.acquire(); - BinaryWriterPool.release(writer); - } - } -} - -/// Benchmark for releasing writers to pool -/// -/// Tests the performance of returning writers to the pool. -class PoolReleaseBenchmark extends BenchmarkBase { - PoolReleaseBenchmark() : super('Pool: release writer'); - - late List writers; - - @override - void setup() { - BinaryWriterPool.clear(); - writers = []; - for (var i = 0; i < 100; i++) { - writers.add(BinaryWriterPool.acquire()); - } - } - - @override - void exercise() => run(); - - @override - void run() { - for (final writer in writers) { - writer.writeBytes(List.generate(50, (j) => j % 256)); - BinaryWriterPool.release(writer); - } - } -} - -/// Benchmark for acquire + write + release cycle -/// -/// Full cycle: get writer, use it, return it to pool. -class PoolFullCycleBenchmark extends BenchmarkBase { - PoolFullCycleBenchmark() - : super('Pool: full cycle (acquire + write + release)'); - - @override - void setup() { - BinaryWriterPool.clear(); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - final writer = BinaryWriterPool.acquire() - ..writeUint32(i) - ..writeString('test message $i') - ..writeBytes(List.generate(32, (j) => (i + j) % 256)); - BinaryWriterPool.release(writer); - } - } -} - -/// Benchmark for heavy writer usage with pool -/// -/// Simulates typical protocol message serialization using pool. -class PoolHeavyUsageBenchmark extends BenchmarkBase { - PoolHeavyUsageBenchmark() : super('Pool: heavy usage (realistic)'); - - @override - void setup() { - BinaryWriterPool.clear(); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 50; i++) { - final writer = BinaryWriterPool.acquire() - // Simulate message header - ..writeUint32(i) // Message ID - ..writeVarUint(i % 1000) // Message length - // Write payload - ..writeString('Header: $i'); - for (var j = 0; j < 5; j++) { - writer.writeFloat64(i * 3.14 + j); - } - writer.writeBytes(List.generate(256, (k) => (i + k) % 256)); - // Return to pool - BinaryWriterPool.release(writer); - } - } -} - -/// Benchmark for sequential acquire operations -/// -/// Tests pool performance under sequential load without much release. -class PoolSequentialAcquireBenchmark extends BenchmarkBase { - PoolSequentialAcquireBenchmark() : super('Pool: sequential acquire'); - - @override - void setup() { - BinaryWriterPool.clear(); - } - - @override - void exercise() => run(); - - @override - void run() { - final writers = []; - // Acquire up to pool max size - for (var i = 0; i < 32; i++) { - writers.add(BinaryWriterPool.acquire()); - } - // Release all - writers.forEach(BinaryWriterPool.release); - } -} - -/// Benchmark for pool statistics queries -/// -/// Tests the performance of checking pool statistics. -class PoolStatisticsBenchmark extends BenchmarkBase { - PoolStatisticsBenchmark() : super('Pool: query statistics'); - - late List writers; - - @override - void setup() { - BinaryWriterPool.clear(); - writers = []; - for (var i = 0; i < 10; i++) { - final w = BinaryWriterPool.acquire() - ..writeBytes(List.generate(100, (j) => j % 256)); - writers.add(w); - } - } - - @override - void exercise() => run(); - - @override - void run() { - // Query statistics multiple times - for (var i = 0; i < 1000; i++) { - // This should ideally be cheap - just reading counters - final stat = BinaryWriterPool.stats; - // Use the stat to prevent optimization away - if (stat.pooled > 0) { - // Just to use the value - } - } - } -} - -/// Benchmark for mixed operations on pool -/// -/// Realistic pattern: acquire, use, release in varying patterns. -class PoolMixedOperationsBenchmark extends BenchmarkBase { - PoolMixedOperationsBenchmark() : super('Pool: mixed operations'); - - @override - void setup() { - BinaryWriterPool.clear(); - } - - @override - void exercise() => run(); - - @override - void run() { - final batch1 = []; - // Acquire batch - for (var i = 0; i < 10; i++) { - batch1.add(BinaryWriterPool.acquire()); - } - // Use first batch - for (final w in batch1) { - w.writeVarUint(42); - } - // Acquire second batch while first still active - final batch2 = []; - for (var i = 0; i < 10; i++) { - batch2.add(BinaryWriterPool.acquire()); - } - // Release first batch - batch1.forEach(BinaryWriterPool.release); - // Continue using second batch - for (final w in batch2) { - w.writeFloat32(3.14); - } - // Release second batch - batch2.forEach(BinaryWriterPool.release); - } -} - -/// Benchmark for pool with buffer reuse -/// -/// Tests how well buffers are reused when writers are recycled. -class PoolBufferReuseBenchmark extends BenchmarkBase { - PoolBufferReuseBenchmark() : super('Pool: buffer reuse efficiency'); - - @override - void setup() { - BinaryWriterPool.clear(); - } - - @override - void exercise() => run(); - - @override - void run() { - // Use pool with varying write sizes - for (var cycle = 0; cycle < 20; cycle++) { - final writer = BinaryWriterPool.acquire(); - // Write varying amount of data - final size = 64 * (cycle % 10 + 1); // 64, 128, 192, ..., 640 - writer.writeBytes(List.generate(size, (i) => i % 256)); - BinaryWriterPool.release(writer); - } - } -} - -/// Benchmark for reset statistics -/// -/// Tests the cost of resetting pool statistics. -class PoolResetStatisticsBenchmark extends BenchmarkBase { - PoolResetStatisticsBenchmark() : super('Pool: reset statistics'); - - @override - void setup() { - BinaryWriterPool.clear(); - // Generate some statistics by using pool - for (var i = 0; i < 100; i++) { - final w = BinaryWriterPool.acquire()..writeUint32(i); - BinaryWriterPool.release(w); - } - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - BinaryWriterPool.clear(); - // Do some work - final w = BinaryWriterPool.acquire()..writeUint32(i); - BinaryWriterPool.release(w); - } - } -} - -void main() { - test('Pool acquire operations:', () { - PoolAcquireNewBenchmark().report(); - PoolAcquireReusedBenchmark().report(); - }, tags: ['benchmark']); - - test('Pool release operations:', () { - PoolReleaseBenchmark().report(); - PoolFullCycleBenchmark().report(); - }, tags: ['benchmark']); - - test('Pool usage patterns:', () { - PoolHeavyUsageBenchmark().report(); - PoolSequentialAcquireBenchmark().report(); - PoolMixedOperationsBenchmark().report(); - }, tags: ['benchmark']); - - test('Pool efficiency:', () { - PoolBufferReuseBenchmark().report(); - PoolStatisticsBenchmark().report(); - PoolResetStatisticsBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/writer/string_write_bench_test.dart b/test/performance/writer/string_write_bench_test.dart deleted file mode 100644 index 58e0783..0000000 --- a/test/performance/writer/string_write_bench_test.dart +++ /dev/null @@ -1,360 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for writing ASCII strings (fast path) -class AsciiStringWriteBenchmark extends BenchmarkBase { - AsciiStringWriteBenchmark() : super('String write: ASCII only'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeString('Hello, World! This is a test string 123456789'); - } - writer.reset(); - } -} - -/// Benchmark for writing short ASCII strings -class ShortAsciiStringWriteBenchmark extends BenchmarkBase { - ShortAsciiStringWriteBenchmark() : super('String write: short ASCII'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 125; i++) { - writer - ..writeString('Hi') - ..writeString('Test') - ..writeString('Hello') - ..writeString('OK') - ..writeString('Error') - ..writeString('Success') - ..writeString('123') - ..writeString('ABC'); - } - writer.reset(); - } -} - -/// Benchmark for writing long ASCII strings -class LongAsciiStringWriteBenchmark extends BenchmarkBase { - LongAsciiStringWriteBenchmark() : super('String write: long ASCII'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - const longString = - 'The quick brown fox jumps over the lazy dog. ' - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.'; - for (var i = 0; i < 100; i++) { - writer.writeString(longString); - } - writer.reset(); - } -} - -/// Benchmark for writing Cyrillic strings (2-byte UTF-8) -class CyrillicStringWriteBenchmark extends BenchmarkBase { - CyrillicStringWriteBenchmark() - : super('String write: Cyrillic (2-byte UTF-8)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeString('ะŸั€ะธะฒะตั‚ ะผะธั€! ะญั‚ะพ ั‚ะตัั‚ะพะฒะฐั ัั‚ั€ะพะบะฐ ะฝะฐ ั€ัƒััะบะพะผ ัะทั‹ะบะต.'); - } - writer.reset(); - } -} - -/// Benchmark for writing CJK strings (3-byte UTF-8) -class CjkStringWriteBenchmark extends BenchmarkBase { - CjkStringWriteBenchmark() : super('String write: CJK (3-byte UTF-8)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeString('ไฝ ๅฅฝไธ–็•Œ๏ผ่ฟ™ๆ˜ฏไธ€ไธชๆต‹่ฏ•ๅญ—็ฌฆไธฒใ€‚ๆ—ฅๆœฌ่ชžใฎใƒ†ใ‚นใƒˆใ‚‚ๅซใพใ‚Œใฆใ„ใพใ™ใ€‚'); - } - writer.reset(); - } -} - -/// Benchmark for writing emoji strings (4-byte UTF-8) -class EmojiStringWriteBenchmark extends BenchmarkBase { - EmojiStringWriteBenchmark() : super('String write: Emoji (4-byte UTF-8)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeString('๐Ÿš€ ๐ŸŒ ๐ŸŽ‰ ๐Ÿ‘ ๐Ÿ’ป ๐Ÿ”ฅ โšก ๐ŸŽฏ ๐Ÿ† ๐Ÿ’ก ๐ŸŒˆ โœจ ๐ŸŽจ ๐ŸŽญ ๐ŸŽช'); - } - writer.reset(); - } -} - -/// Benchmark for writing mixed Unicode strings -class MixedUnicodeStringWriteBenchmark extends BenchmarkBase { - MixedUnicodeStringWriteBenchmark() : super('String write: mixed Unicode'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeString('Hello ะผะธั€ ไธ–็•Œ ๐ŸŒ! Test ั‚ะตัั‚ ๆต‹่ฏ• ๐Ÿš€'); - } - writer.reset(); - } -} - -/// Benchmark for writing VarString (length-prefixed strings) -class VarStringAsciiWriteBenchmark extends BenchmarkBase { - VarStringAsciiWriteBenchmark() : super('VarString write: ASCII'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeVarString('Hello, World! This is a test string.'); - } - writer.reset(); - } -} - -/// Benchmark for writing VarString with mixed Unicode -class VarStringMixedWriteBenchmark extends BenchmarkBase { - VarStringMixedWriteBenchmark() : super('VarString write: mixed Unicode'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer.writeVarString('Hello ะผะธั€ ไธ–็•Œ ๐ŸŒ Test ั‚ะตัั‚ ๆต‹่ฏ• ๐Ÿš€'); - } - writer.reset(); - } -} - -/// Benchmark for writing empty strings -class EmptyStringWriteBenchmark extends BenchmarkBase { - EmptyStringWriteBenchmark() : super('String write: empty strings'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 8192); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeString(''); - } - writer.reset(); - } -} - -/// Benchmark for realistic message protocol with strings -class RealisticMessageWriteBenchmark extends BenchmarkBase { - RealisticMessageWriteBenchmark() : super('String write: realistic message'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 100; i++) { - writer - ..writeString('user') - ..writeString('John Doe') - ..writeString('email') - ..writeString('john.doe@example.com') - ..writeString('message') - ..writeString('Hello ไธ–็•Œ! ๐ŸŒ') - ..writeString('timestamp') - ..writeString('2024-12-30T12:00:00Z') - ..writeString('locale') - ..writeString('ru-RU'); - } - writer.reset(); - } -} - -/// Benchmark for alternating short and long strings -class AlternatingStringWriteBenchmark extends BenchmarkBase { - AlternatingStringWriteBenchmark() - : super('String write: alternating lengths'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - const shortString = 'Hi'; - const longString = - 'This is a much longer string with more content to write and process'; - for (var i = 0; i < 500; i++) { - writer - ..writeString(shortString) - ..writeString(longString); - } - writer.reset(); - } -} - -/// Benchmark for writing very long strings (> 1KB) -class VeryLongStringWriteBenchmark extends BenchmarkBase { - VeryLongStringWriteBenchmark() : super('String write: very long (>1KB)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 65536); - } - - @override - void exercise() => run(); - - @override - void run() { - final longString = 'Lorem ipsum dolor sit amet. ' * 80; - for (var i = 0; i < 50; i++) { - writer.writeString(longString); - } - writer.reset(); - } -} - -void main() { - test('ASCII string benchmarks:', () { - AsciiStringWriteBenchmark().report(); - ShortAsciiStringWriteBenchmark().report(); - LongAsciiStringWriteBenchmark().report(); - EmptyStringWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('UTF-8 multi-byte benchmarks:', () { - CyrillicStringWriteBenchmark().report(); - CjkStringWriteBenchmark().report(); - EmojiStringWriteBenchmark().report(); - MixedUnicodeStringWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('VarString benchmarks:', () { - VarStringAsciiWriteBenchmark().report(); - VarStringMixedWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('Realistic string scenarios:', () { - RealisticMessageWriteBenchmark().report(); - AlternatingStringWriteBenchmark().report(); - VeryLongStringWriteBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/performance/writer/varint_write_bench_test.dart b/test/performance/writer/varint_write_bench_test.dart deleted file mode 100644 index 709986d..0000000 --- a/test/performance/writer/varint_write_bench_test.dart +++ /dev/null @@ -1,242 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:pro_binary/pro_binary.dart'; -import 'package:test/test.dart'; - -/// Benchmark for writing VarUint in fast path (0-127) -class VarUintFastPathWriteBenchmark extends BenchmarkBase { - VarUintFastPathWriteBenchmark() : super('VarUint write: 0-127 (fast path)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(i % 128); - } - writer.reset(); - } -} - -/// Benchmark for writing VarUint 2-byte values -class VarUint2ByteWriteBenchmark extends BenchmarkBase { - VarUint2ByteWriteBenchmark() : super('VarUint write: 128-16383 (2 bytes)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(128 + (i % 1000)); - } - writer.reset(); - } -} - -/// Benchmark for writing VarUint 3-byte values -class VarUint3ByteWriteBenchmark extends BenchmarkBase { - VarUint3ByteWriteBenchmark() - : super('VarUint write: 16384-2097151 (3 bytes)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(16384 + (i % 10000)); - } - writer.reset(); - } -} - -/// Benchmark for writing VarUint 4-byte values -class VarUint4ByteWriteBenchmark extends BenchmarkBase { - VarUint4ByteWriteBenchmark() - : super('VarUint write: 2097152-268435455 (4 bytes)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(2097152 + (i % 100000)); - } - writer.reset(); - } -} - -/// Benchmark for writing VarUint 5-byte values -class VarUint5ByteWriteBenchmark extends BenchmarkBase { - VarUint5ByteWriteBenchmark() : super('VarUint write: 268435456+ (5 bytes)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarUint(268435456 + i); - } - writer.reset(); - } -} - -/// Benchmark for writing positive VarInt (ZigZag encoded) -class VarIntPositiveWriteBenchmark extends BenchmarkBase { - VarIntPositiveWriteBenchmark() : super('VarInt write: positive (ZigZag)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarInt(i); - } - writer.reset(); - } -} - -/// Benchmark for writing negative VarInt (ZigZag encoded) -class VarIntNegativeWriteBenchmark extends BenchmarkBase { - VarIntNegativeWriteBenchmark() : super('VarInt write: negative (ZigZag)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarInt(-(i + 1)); - } - writer.reset(); - } -} - -/// Benchmark for writing mixed positive/negative VarInt -class VarIntMixedWriteBenchmark extends BenchmarkBase { - VarIntMixedWriteBenchmark() : super('VarInt write: mixed positive/negative'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 16384); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - writer.writeVarInt(i.isEven ? i : -i); - } - writer.reset(); - } -} - -/// Benchmark for realistic VarUint distribution -class VarUintMixedSizesWriteBenchmark extends BenchmarkBase { - VarUintMixedSizesWriteBenchmark() - : super('VarUint write: mixed sizes (realistic)'); - - late BinaryWriter writer; - - @override - void setup() { - writer = BinaryWriter(initialBufferSize: 32768); - } - - @override - void exercise() => run(); - - @override - void run() { - for (var i = 0; i < 1000; i++) { - final mod = i % 100; - if (mod < 70) { - writer.writeVarUint(i % 128); - } else if (mod < 90) { - writer.writeVarUint(128 + (i % 1000)); - } else if (mod < 98) { - writer.writeVarUint(16384 + (i % 10000)); - } else { - writer.writeVarUint(2097152 + i); - } - } - writer.reset(); - } -} - -void main() { - test('VarUint size benchmarks:', () { - VarUintFastPathWriteBenchmark().report(); - VarUint2ByteWriteBenchmark().report(); - VarUint3ByteWriteBenchmark().report(); - VarUint4ByteWriteBenchmark().report(); - VarUint5ByteWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('VarInt (ZigZag) benchmarks:', () { - VarIntPositiveWriteBenchmark().report(); - VarIntNegativeWriteBenchmark().report(); - VarIntMixedWriteBenchmark().report(); - }, tags: ['benchmark']); - - test('Realistic scenarios:', () { - VarUintMixedSizesWriteBenchmark().report(); - }, tags: ['benchmark']); -} diff --git a/test/unit/binary_reader_test.dart b/test/unit/binary_reader_test.dart index 8035658..91579ad 100644 --- a/test/unit/binary_reader_test.dart +++ b/test/unit/binary_reader_test.dart @@ -2036,5 +2036,62 @@ void main() { } }); }); + + group('Concise API', () { + test('operator [] returns byte at absolute index', () { + final buffer = Uint8List.fromList([10, 20, 30, 40]); + final reader = BinaryReader(buffer); + expect(reader[0], equals(10)); + expect(reader[1], equals(20)); + expect(reader[2], equals(30)); + expect(reader[3], equals(40)); + expect(reader.offset, equals(0)); + }); + + test('call() is an alias for readBytes', () { + final buffer = Uint8List.fromList([10, 20, 30, 40]); + final reader = BinaryReader(buffer); + final data = reader.call(2); + expect(data, equals([10, 20])); + expect(reader.offset, equals(2)); + }); + }); + + group('Coverage edge cases', () { + test('readVarUint throws on empty buffer', () { + final reader = BinaryReader(Uint8List(0)); + expect(reader.readVarUint, throwsA(isA())); + }); + + test('readVarUint throws on truncated 3-byte value', () { + final reader = BinaryReader(Uint8List.fromList([0x80, 0x80])); + expect(reader.readVarUint, throwsA(isA())); + }); + + test('readVarUint throws on truncated multi-byte value in loop', () { + final reader = BinaryReader( + Uint8List.fromList([0x80, 0x80, 0x80, 0x80]), + ); + expect(reader.readVarUint, throwsA(isA())); + }); + + test('hasBytes throws on negative length', () { + final reader = BinaryReader(Uint8List(10)); + expect(() => reader.hasBytes(-1), throwsA(isA())); + }); + + test('readBytes throws on negative length', () { + final reader = BinaryReader(Uint8List(10)); + expect(() => reader.readBytes(-1), throwsA(isA())); + }); + + test( + 'seek throws on out of range offset in checkBounds via peekBytes', + () { + final reader = BinaryReader(Uint8List(10)); + expect(() => reader.peekBytes(1, 11), throwsA(isA())); + }, + ); + }); }); } diff --git a/test/unit/binary_writer_test.dart b/test/unit/binary_writer_test.dart index 07da45f..7db4397 100644 --- a/test/unit/binary_writer_test.dart +++ b/test/unit/binary_writer_test.dart @@ -2776,5 +2776,92 @@ void main() { expect(reader.readUint64(), equals(123456789)); expect(reader.availableBytes, equals(0)); }); + + group('Concise API', () { + test('call() is an alias for writeBytes', () { + final writer = BinaryWriter(); + writer([10, 20, 30]); + expect(writer.takeBytes(), equals([10, 20, 30])); + }); + }); + + group('Extra error handling', () { + test( + 'writeString handles high surrogate followed by non-low surrogate', + () { + final writer = BinaryWriter(); + const testStr = 'A\uD800B'; + writer.writeString(testStr); + final bytes = writer.takeBytes(); + expect(bytes.length, equals(5)); + expect(bytes[0], equals(0x41)); // 'A' + expect(bytes[1], equals(0xEF)); // U+FFFD start + expect(bytes[4], equals(0x42)); // 'B' + }, + ); + + test( + 'writeString handles lone high surrogate at end of string', + () { + final writer = BinaryWriter(); + const testStr = 'A\uD800'; + writer.writeString(testStr); + final bytes = writer.takeBytes(); + expect(bytes.length, equals(4)); + }, + ); + }); + + group('BinaryWriterPool Extra', () { + test('withWriter executes action and releases writer', () { + final result = BinaryWriterPool.withWriter((w) { + w.writeUint32(42); + return w.toBytes(); + }); + expect(result, equals([0, 0, 0, 42])); + expect(BinaryWriterPool.stats.pooled, greaterThan(0)); + }); + + test('withWriter releases writer even on error', () { + BinaryWriterPool.clear(); + expect( + () => BinaryWriterPool.withWriter((w) { + throw Exception('Test'); + }), + throwsException, + ); + expect(BinaryWriterPool.stats.pooled, equals(1)); + }); + + test('acquire expands pooled writer if requested size is larger', () { + BinaryWriterPool.clear(); + final writer = BinaryWriterPool.acquire(100); + BinaryWriterPool.release(writer); + + // Now acquire with larger buffer + final largerWriter = BinaryWriterPool.acquire(1000); + expect(largerWriter.capacity, greaterThanOrEqualTo(1000)); + BinaryWriterPool.release(largerWriter); + }); + }); + + group('Coverage edge cases', () { + test('writeVarString with 16KB+ string (2-byte VarInt)', () { + final writer = BinaryWriter(); + final longStr = 'a' * 20000; + writer.writeVarString(longStr); + final reader = BinaryReader(writer.takeBytes()); + expect(reader.readVarString(), equals(longStr)); + }); + + test('writeVarString with 2MB+ string (3-byte VarInt)', () { + final writer = BinaryWriter(); + // This is a large test, but necessary for coverage + final longStr = 'a' * (256 * 1024); + writer.writeVarString(longStr); + final reader = BinaryReader(writer.takeBytes()); + expect(reader.readVarString(), equals(longStr)); + }); + }); }); }