Co-Authored-By: Claude <noreply@anthropic.com>
PureBasic - MsgPack - Performances
Performance benchmarks for the PB-MessagePack include.
Please refer to the Performances section for the warnings and conclusions.
Open questions raised by these results are tracked in Investigations.md.
Workloads
Each folder has a RunPerfTests.ps1 script that runs its benchmarks:
MassiveReadAmount/- 1M small fields written, read and skipped, with explicit and implicit typing.StringsAndBinary/- Strings of 8 B to 64 KB, and binaries of 1 KB and 1 MB, written and read.FlatMaps/- 100K maps of 16 entries written and read.DeepNesting/- Arrays nested 1 to 128 levels deep, built, examined and measured. (PureBasic only)NullChecks/- Small fields written and read with and without#MsgPack_DisableNullChecks. (PureBasic only)
The first three are compared to msgpack (Python) and MessagePack-CSharp (.NET).
The scripts prompt for the PureBasic version, architecture and backend.
The results are saved in Results/, as one CSV per folder, PureBasic version, architecture and backend.
Results
Hardware:
- Proxmox VM running Windows 10
- 10 Intel Core i5-13500 cores (No E/P core pinning)
- PureBasic 6.41
- Python 3.14 with msgpack 1.2.3
- .NET 10 with MessagePack-CSharp 3.1.12
Times are in nanoseconds per field, per map or per nested structure, lower is better.
Every implementation produced the same bytes and read back the same values.
PureBasic 6.41 is used everywhere, except in the last table.
Compared to other implementations
x64 with the C backend, the fastest of each row in bold.
| Workload | Op | PureBasic | Python | .NET |
|---|---|---|---|---|
| 1M small fields 1 | Write | 11.9 | 50.9 | 5.4 |
| Read | 12.7 | 52.4 | 10.6 | |
| Skip | 18.3 | 40.2 | 29.4 | |
| Strings, 8 B | Write | 59.6 | 62.0 | 13.2 |
| Read | 46.6 | 73.4 | 16.2 | |
| Strings, 1 KB | Write | 720.9 | 556.0 | 804.8 |
| Read | 570.0 | 341.3 | 150.0 | |
| Strings, 64 KB | Write | 45,917 | 33,932 | 39,848 |
| Read | 31,473 | 16,390 | 23,045 | |
| Binary, 1 KB | Write | 190.6 | 556.6 | 576.1 |
| Read | 50.2 | 311.3 | 204.0 | |
| Binary, 1 MB | Write | 207,744 | 574,825 | 438,025 |
| Read | 54,812 | 465,075 | 59,459 | |
| Maps of 16 entries | Write | 1,793 | 927.2 | 209.2 |
| Read | 1,858 | 1,962 | 292.1 |
1 1M fields of mixed integers, floats and short strings, with implicit typing. PureBasic writes into a preallocated buffer.
Architectures & backends
PureBasic only.
| Scenario | x64 C | x64 ASM | x86 C |
|---|---|---|---|
| Small fields, explicit write | 11.0 | 18.0 | 13.9 |
| Small fields, explicit read | 9.8 | 15.9 | 8.8 |
| Small fields, implicit write | 11.9 | 25.4 | 14.2 |
| Small fields, implicit read | 12.7 | 25.3 | 11.5 |
| Small fields, skip | 18.3 | 57.9 | 23.2 |
| Small fields, write, 64 KB growth 2 | 37.9 | 51.5 | 44.7 |
| Small fields, write, exact growth 2 | 441.0 | 461.6 | 503.3 |
| Small fields, read, null checks off 3 | 11.4 | 19.5 | 11.4 |
| Small fields, read, null checks on 3 | 12.2 | 20.2 | 11.4 |
| Strings, 1 KB UTF-16, write | 526.0 | 539.1 | 599.0 |
| Strings, 1 KB UTF-16, read | 282.7 | 298.8 | 289.1 |
| Maps of 16 entries, write | 1,793 | 3,472 | 2,189 |
| Maps of 16 entries, read | 1,858 | 3,232 | 1,782 |
2 The buffer starts at 64 bytes and grows in multiples of 64 KB, or by the exact size of each write. (GrowthIncrement of 1)
3 With and without #MsgPack_DisableNullChecks.
Deep nesting
x64 with the C backend, arrays of 3 integers nested N levels deep.
| Depth | Build | Examine | Examine, unsafe | MsgPackGetFieldFullSize |
|---|---|---|---|---|
| 1 | 283.3 | 177.8 | 134.9 | 80.4 |
| 2 | 453.4 | 363.2 | 261.4 | 115.8 |
| 4 | 1,002 | 825.2 | 464.1 | 177.3 |
| 8 | 2,226 | 2,154 | 906.9 | 308.9 |
| 16 | 6,414 | 6,155 | 1,807 | 546.5 |
| 32 | 21,888 | 21,134 | 4,367 | 985.9 |
| 64 | 76,370 | 72,715 | 7,774 | 1,997 |
| 128 | 278,388 | 269,656 | 15,803 | 3,812 |
PureBasic 6.21 vs 6.41
x64 with the C backend, the new string handling of 6.40 being the main difference.
| Scenario | 6.21 | 6.41 | Speedup |
|---|---|---|---|
| Small fields, implicit write 4 | 23.0 | 11.9 | 1.9x |
| Small fields, implicit read 4 | 26.4 | 12.7 | 2.1x |
| Small fields, skip | 14.7 | 18.3 | 0.8x |
| Strings, 8 B, write | 162.8 | 59.6 | 2.7x |
| Strings, 8 B, read | 164.5 | 46.6 | 3.5x |
| Strings, 1 KB, write | 1,971 | 720.9 | 2.7x |
| Strings, 1 KB, read | 1,412 | 570.0 | 2.5x |
| Strings, 64 KB, write | 113,872 | 45,917 | 2.5x |
| Strings, 64 KB, read | 77,056 | 31,473 | 2.4x |
| Strings, 1 KB UTF-16, write | 2,026 | 526.0 | 3.9x |
| Strings, 1 KB UTF-16, read | 1,065 | 282.7 | 3.8x |
| Binary, 1 KB, write | 173.4 | 190.6 | 0.9x |
| Binary, 1 KB, read | 41.2 | 50.2 | 0.8x |
| Maps of 16 entries, write 4 | 3,221 | 1,793 | 1.8x |
| Maps of 16 entries, read 4 | 3,478 | 1,858 | 1.9x |
| Nested 128 levels, examine | 263,158 | 269,656 | 1.0x |
4 Includes strings, one per record of 8 fields, and one per map key.
The x64 ASM and x86 C results show the same 2 to 3 times speedup on strings.