Cython-accelerated SCALE codec library for Substrate-based blockchains (Polkadot, Kusama, Bittensor, etc.).
A drop-in replacement for py-scale-codec — same scalecodec module name, same public API, compiled with Cython for improved throughput.
pip install cyscaleBenchmarked on Apple M-series (Python 3.13) against py-scale-codec 1.2.12. All timings are µs per call; speedup = py ÷ cy.
| Benchmark | py (µs) | cy (µs) | speedup |
|---|---|---|---|
| u8 decode | 3.01 | 1.16 | 2.60× |
| u16 decode | 2.99 | 1.25 | 2.40× |
| u32 decode | 3.11 | 1.26 | 2.47× |
| u64 decode | 3.09 | 1.22 | 2.53× |
| u128 decode | 2.91 | 1.22 | 2.39× |
| Compact decode | 9.59 | 4.38 | 2.19× |
| bool decode | 2.93 | 1.18 | 2.48× |
| H256 decode | 2.93 | 1.21 | 2.41× |
| AccountId decode (SS58 format 42) | 11.45 | 6.11 | 1.87× |
| Str decode | 12.89 | 5.82 | 2.22× |
| (u32, u64, bool) decode | 21.96 | 5.59 | 3.93× |
| u32 encode | 2.34 | 1.10 | 2.13× |
| u64 encode | 2.33 | 1.20 | 1.93× |
| Compact encode | 8.85 | 4.42 | 2.00× |
| H256 encode | 2.47 | 1.01 | 2.44× |
| Benchmark | py (µs) | cy (µs) | speedup |
|---|---|---|---|
| Vec decode (64 elements) | 224.20 | 98.13 | 2.28× |
| Vec decode (1,024 elements) | 3217.32 | 1423.46 | 2.26× |
| Vec decode (16,384 elements) | 50396.95 | 22435.45 | 2.25× |
| Bytes decode (1 KB) | 14.67 | 6.87 | 2.14× |
| Bytes decode (64 KB) | 64.15 | 45.05 | 1.42× |
| Bytes decode (512 KB) | 379.99 | 300.14 | 1.27× |
| Vec decode (5 events, V10) | 301.10 | 135.32 | 2.23× |
| MetadataVersioned decode (V10, 85 KB) | 64958.83 | 29839.68 | 2.18× |
| MetadataVersioned decode (V13, 219 KB) | 143029.99 | 65651.69 | 2.18× |
| MetadataVersioned decode (V14, 300 KB) | 390902.34 | 183644.98 | 2.13× |
| Bittensor metadata + portable registry (254 KB) | 443089.47 | 212345.78 | 2.09× |
Primitives and small types see ~2.0–2.6× speedup. Large metadata decoding
sees ~2.1–2.3× speedup — the gain compounds across thousands of recursive
decode calls. Raw bulk byte operations (Bytes/Vec<u8>) above ~64 KB are
dominated by memcpy and see a reduced ~1.3–1.4× speedup.
AccountId with SS58 encoding shows a 1.87× speedup — the SS58 encoding
itself (ss58_encode) is pure Python and limits gains in that path.
batch_decode(type_strings, bytes_list) amortises Python dispatch overhead
across a list of decodes. The baseline below is a py-scale-codec decode loop,
which is the equivalent operation without this API.
Note: bt_decode is excluded from this comparison because it does not perform
SS58 encoding — including it without that post-processing step would be unfair.
| Benchmark | py loop (µs) | cy batch (µs) | speedup |
|---|---|---|---|
| batch_decode AccountId ×10 | 116.66 | 42.07 | 2.77× |
| batch_decode AccountId ×100 | 1159.47 | 415.44 | 2.79× |
| batch_decode AccountId ×1,000 | 11530.59 | 4112.27 | 2.80× |
| Mixed (AccountId/u32/u128) ×100 | 599.73 | 147.75 | 4.06× |
To reproduce, run:
# save a py-scale-codec baseline
python benchmarks/bench.py --save-baseline benchmarks/baseline_py.json
# compare against cy-scale-codec
PYTHONPATH=. python benchmarks/bench.py --compare benchmarks/baseline_py.json| Type | Description | Example SCALE decoding value | SCALE encoded value |
|---|---|---|---|
bool |
Boolean values are encoded using the least significant bit of a single byte. | True |
0x01 |
u16 |
Basic integers are encoded using a fixed-width little-endian (LE) format. | 42 |
0x2a00 |
Compact |
A "compact" or general integer encoding is sufficient for encoding large integers (up to 2**536) and is more efficient at encoding most values than the fixed-width version. | 1 |
0x04 |
Vec |
A collection of same-typed values is encoded, prefixed with a compact encoding of the number of items, followed by each item's encoding concatenated in turn. | [4, 8, 15, 16, 23, 42] |
0x18040008000f00100017002a00 |
str, Bytes |
Strings are Vectors of bytes (Vec<u8>) containing a valid UTF8 sequence. |
"Test" |
0x1054657374 |
AccountId |
An SS58 formatted representation of an account. | "5GDyPHLVHcQYPTWfygtPYeogQjyZy7J9fsi4brPhgEFq4pcv" |
0xb80269ec... |
Enum |
A fixed number of variants, each mutually exclusive. Encoded as the first byte identifying the index of the variant. | {'Int': 8} |
0x002a |
Struct |
For structures, values are named but that is irrelevant for the encoding (only order matters). | {"votes": [...], "id": 4} |
0x04b80269... |