Skip to content

Commit c574542

Browse files
committed
test: add a throughput benchmark
1 KiB throughput benchmark; representative numbers added to the README.
1 parent a61a989 commit c574542

4 files changed

Lines changed: 147 additions & 0 deletions

File tree

.pubignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# Dev-only conformance tooling; not part of the published package.
22
tool/
3+
4+
# Dev-only throughput benchmarks; not part of the published package.
5+
benchmark/

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@ same. Compare PPP/SLIP escape stuffing, whose worst case **doubles** the packet.
222222
> `cobsUnframe` or `CobsFrameDecoder`). This matches the reference COBS
223223
> implementations, where framing is the application's responsibility.
224224
225+
## Benchmarks
226+
227+
Single-threaded throughput on a 1 KiB payload
228+
(`dart run benchmark/cobs_benchmark.dart`), Dart 3.x on an AMD Ryzen 7 3800XT
229+
under WSL2 — indicative, not a controlled benchmark:
230+
231+
| Operation | Throughput |
232+
| --------- | ---------- |
233+
| `cobsEncode` | ~580 MB/s |
234+
| `cobsDecode` | ~570 MB/s |
235+
| `cobsrEncode` | ~610 MB/s |
236+
225237
## Background
226238

227239
COBS is described in:

benchmark/cobs_benchmark.dart

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) 2026 Alexander Salas Bastidas <ajsb85@firechip.dev>
2+
// SPDX-License-Identifier: MIT
3+
4+
/// Throughput micro-benchmarks for the COBS / COBS/R codecs.
5+
///
6+
/// Dev tooling only: this file is excluded from the published package via
7+
/// `.pubignore` and is not part of the public API.
8+
///
9+
/// Run with:
10+
///
11+
/// ```sh
12+
/// dart run benchmark/cobs_benchmark.dart
13+
/// ```
14+
///
15+
/// Each benchmark processes a single 1 KiB (1024-byte) payload per `run()`.
16+
/// `exercise()` is overridden to invoke `run()` exactly once, so
17+
/// `BenchmarkBase.measure()` returns microseconds per operation. Throughput is
18+
/// reported as `payloadBytes / time`; because one byte per microsecond equals
19+
/// one decimal megabyte per second, `MB/s == payloadBytes / usPerOp`.
20+
library;
21+
22+
import 'dart:io';
23+
import 'dart:typed_data';
24+
25+
import 'package:benchmark_harness/benchmark_harness.dart';
26+
import 'package:cobs_codec/cobs_codec.dart';
27+
28+
/// Size of the representative payload, in bytes (1 KiB).
29+
const int payloadBytes = 1024;
30+
31+
/// Builds a deterministic, mostly-non-zero payload with roughly one in eight
32+
/// bytes set to `0x00`, so the COBS zero-delimited block path is exercised.
33+
///
34+
/// Uses a fixed-seed xorshift32 generator so the payload is identical across
35+
/// runs and platforms, making results comparable over time.
36+
Uint8List buildPayload(int length) {
37+
final data = Uint8List(length);
38+
var state = 0x12345678; // fixed seed -> reproducible pattern
39+
for (var i = 0; i < length; i++) {
40+
state ^= (state << 13) & 0xFFFFFFFF;
41+
state ^= state >> 17;
42+
state ^= (state << 5) & 0xFFFFFFFF;
43+
final b = state & 0xFF;
44+
// ~1/8 of bytes become zero; the rest keep a guaranteed-non-zero value.
45+
data[i] = (b & 7) == 0 ? 0 : b;
46+
}
47+
return data;
48+
}
49+
50+
/// Benchmarks a full COBS encode of the 1 KiB payload.
51+
class CobsEncodeBenchmark extends BenchmarkBase {
52+
CobsEncodeBenchmark(this.payload) : super('cobsEncode');
53+
54+
final Uint8List payload;
55+
56+
@override
57+
void exercise() => run();
58+
59+
@override
60+
void run() {
61+
cobsEncode(payload);
62+
}
63+
}
64+
65+
/// Benchmarks a full COBS decode of the encoded 1 KiB payload.
66+
class CobsDecodeBenchmark extends BenchmarkBase {
67+
CobsDecodeBenchmark(this.encoded) : super('cobsDecode');
68+
69+
final Uint8List encoded;
70+
71+
@override
72+
void exercise() => run();
73+
74+
@override
75+
void run() {
76+
cobsDecode(encoded);
77+
}
78+
}
79+
80+
/// Benchmarks a full COBS/R encode of the 1 KiB payload.
81+
class CobsrEncodeBenchmark extends BenchmarkBase {
82+
CobsrEncodeBenchmark(this.payload) : super('cobsrEncode');
83+
84+
final Uint8List payload;
85+
86+
@override
87+
void exercise() => run();
88+
89+
@override
90+
void run() {
91+
cobsrEncode(payload);
92+
}
93+
}
94+
95+
void main() {
96+
final payload = buildPayload(payloadBytes);
97+
final encoded = cobsEncode(payload);
98+
final zeros = payload.where((b) => b == 0).length;
99+
100+
stdout.writeln('cobs_codec throughput benchmark');
101+
stdout.writeln(' Dart: ${Platform.version}');
102+
stdout.writeln(
103+
' OS: ${Platform.operatingSystem} '
104+
'(${Platform.operatingSystemVersion})',
105+
);
106+
stdout.writeln(
107+
' Payload: $payloadBytes bytes '
108+
'($zeros zero / ${payloadBytes - zeros} non-zero), '
109+
'COBS-encoded to ${encoded.length} bytes',
110+
);
111+
stdout.writeln();
112+
113+
final benchmarks = <BenchmarkBase>[
114+
CobsEncodeBenchmark(payload),
115+
CobsDecodeBenchmark(encoded),
116+
CobsrEncodeBenchmark(payload),
117+
];
118+
119+
stdout.writeln(' benchmark us/op MB/s');
120+
stdout.writeln(' ----------- -------- ---------');
121+
for (final benchmark in benchmarks) {
122+
final usPerOp = benchmark.measure(); // microseconds per single run()
123+
// 1 byte/us == 1e6 bytes/s == 1 MB/s (decimal), so MB/s == bytes / us.
124+
final mbPerSecond = payloadBytes / usPerOp;
125+
stdout.writeln(
126+
' ${benchmark.name.padRight(11)} '
127+
'${usPerOp.toStringAsFixed(3).padLeft(8)} '
128+
'${mbPerSecond.toStringAsFixed(1).padLeft(9)}',
129+
);
130+
}
131+
}

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ environment:
1818
sdk: ^3.5.0
1919

2020
dev_dependencies:
21+
benchmark_harness: ^2.4.0
2122
lints: ">=5.0.0 <7.0.0"
2223
test: ^1.25.0

0 commit comments

Comments
 (0)