Skip to content

Latest commit

 

History

History
518 lines (361 loc) · 26.6 KB

CHANGES.md

File metadata and controls

518 lines (361 loc) · 26.6 KB

Change Log

Version 3.5.0

12.10.2026

🐍 Python 3.15 and free-threaded Python 3.14t / 3.15t wheels

⚡ Up to 2x faster DAG-CBOR encode, up to 10% faster decode

Encode v3.4.1 v3.5.0 Speedup
citm_catalog 1.04 ms 0.52 ms 2.01x (+101%)
github 0.05 ms 0.03 ms 1.78x (+78%)
twitter 0.47 ms 0.27 ms 1.74x (+74%)
canada 0.83 ms 0.64 ms 1.29x (+29%)
Decode v3.4.1 v3.5.0 Speedup
canada 4.22 ms 3.82 ms 1.10x (+10%)
torture_cids 63.5 ms 59.0 ms 1.08x (+8%)
github 0.07 ms 0.06 ms 1.06x (+6%)
atproto_repo.car 65.0 ms 61.8 ms 1.05x (+5%)
twitter 0.77 ms 0.75 ms 1.02x (+2%)
citm_catalog 1.71 ms 1.71 ms 1.00x (+0%)

❗ Behavior changes: -0.0 is now rejected on decode and encoded as 0.0, as the DAG-CBOR spec requires; encoding deeply nested data raises RecursionError instead of crashing; malformed CAR header types raise ValueError.

  • Add Python 3.15 support; bump PyO3 0.29 and deps, adopt Ruff 0.17 default rules by @MarshalX in #129
  • Add free-threaded wheels for Python 3.14t and 3.15t by @MarshalX in #130
  • Ship py.typed so type checkers pick up the bundled stubs by @MarshalX
  • Optimize DAG-CBOR encode by reusing a thread-local buffer and validating CIDs without Multihash construction by @MarshalX in #116
  • Optimize DAG-CBOR decode by reading each CBOR header once instead of re-decoding it per value by @MarshalX in #118
  • Optimize DAG-CBOR decode by reading multi-byte header arguments at a fixed width by @MarshalX in #121
  • Optimize DAG-CBOR encode by sorting map entries on a pooled stack and borrowing container items by @MarshalX in #122
  • Optimize DAG-CBOR encode/decode by dispatching leaf values inline and recursing only for containers by @MarshalX in #123
  • Forbid negative zero in DAG-CBOR: reject -0.0 on decode and encode it as 0.0 by @MarshalX in #126
  • Raise RecursionError instead of overflowing the stack when encoding deeply nested DAG-CBOR by @MarshalX in #128
  • Document raised exceptions and normalize CAR header type errors to ValueError by @MarshalX in #127
  • Restructure single-file lib.rs into per-domain modules by @MarshalX in #114
  • Replace _PyDict_SetItem_KnownHash with public PyDict_SetItem by @MarshalX in #124
  • Fix decode error handling: PyList leak, unbounded preallocation, and swallowed errors in decode_dag_cbor_multi by @MarshalX in #117
  • Fix list and bytearray races under free-threading by @MarshalX in #130
  • Fix Windows build after PyO3 0.29 by @MarshalX in #131
  • Fix encode_multibase docs: 'u' is base64url, not base58btc by @MarshalX

Version 3.4.1

27.05.2026

This is a focused decode-side optimization. The numbers below come from pytest-benchmark on Apple M1 / CPython 3.14 (PGO-built wheels), comparing v3.4.0 → v3.4.1 head-to-head.

⚡ Decode (DAG-CBOR)

Fixture v3.4.0 v3.4.1 Speedup
canada 4.75 ms 4.43 ms 1.07x (+7%)
github 69 µs 66 µs 1.05x (+5%)
twitter 790 µs 766 µs 1.03x (+3%)
citm_catalog 1.79 ms 1.77 ms 1.01x (+1%)

The decoder used to fetch the Python recursion limit on every recursive step. We now read it once per top-level decode call and pass it through, closing the last decode gap with cbrrr (we're within 0.7% of it on canada and 30–40% ahead on the others).

🔧 Optimizations

  • Optimize DAG-CBOR decode by reading Py_GetRecursionLimit once per call by @MarshalX in #112

Deserialization v3.4.0 → v3.4.1

image

Serialization v3.4.0 → v3.4.1

image

Version 3.4.0

26.05.2026

❗ Up to ~2x faster DAG-CBOR encode and decode!

This release is all about throughput. The numbers below come from pytest-benchmark on an Apple M1 / CPython 3.14, comparing v3.3.2 → v3.4.0 head-to-head.

⚡ Decode (DAG-CBOR)

Fixture v3.3.2 v3.4.0 Speedup
github 138 µs 70 µs 1.96x (+96%)
twitter 1.44 ms 0.80 ms 1.81x (+81%)
citm_catalog 2.87 ms 1.81 ms 1.59x (+59%)
canada 5.49 ms 4.77 ms 1.15x (+15%)

⚡ Encode (DAG-CBOR)

Fixture v3.3.2 v3.4.0 Speedup
canada 2.31 ms 1.17 ms 1.98x (+98%)
citm_catalog 2.25 ms 1.26 ms 1.79x (+79%)
twitter 902 µs 635 µs 1.42x (+42%)
github 75.4 µs 69.7 µs 1.08x (+8%)

🔧 Optimizations

  • Optimize DAG-CBOR encode/decode and CAR block parsing by @MarshalX in #104
  • Optimize DAG-CBOR string decode with an ASCII fast path by @MarshalX in #105
  • Optimize DAG-CBOR map decode with a key-intern cache and KnownHash insert by @MarshalX in #106
  • Optimize int encode with a direct PyLong digit read by @MarshalX in #107

🧰 Toolchain & Build

  • Bump to Rust 1.95 and PyO3 0.28 by @MarshalX in #102
  • Drop the macos-13 PGO runner; cross-compile x86_64 macOS wheels from arm64 by @MarshalX in #108
  • Bump CI actions (Node 24) and pytest-codspeed v5 by @MarshalX in #103

📊 Benchmarks & Docs

  • Add a head-to-head benchmark against other Python DAG-CBOR libs by @MarshalX in #109
  • Update README and copyright years by @MarshalX in #110

Deserialization v3.3.2 → v3.4.0

image

Serialization v3.3.2 → v3.4.0

image
🏎️ Old-style performance comparison. Before (v3.3.2) VS After (v3.4.0): image

Version 3.3.2

05.12.2025

Version 3.3.1

03.12.2025

  • Add DASL testing harnesses to CI workflow by @MarshalX in #97
  • Fix error handling in array and map length checks by @MarshalX in #95
  • Fix error handling in CID and ULEB128 processing functions by @MarshalX in #96

Version 3.3.0

01.12.2025

❗ Almost x2 speed for CAR decoding (Bluesky firehose), ~25-35% faster DAG-CBOR decoding, and ~17% faster CID decoding!

I do have more ideas on what to try to speed it up even further! For example, applying BOLT on top of the existing PGO.

⚡ Optimizations

  • Huge performance gain for DAG-CBOR decoding and CID by replacing deprecated Rust crate libipld with cbor4ii by @vmx in #80 and even contributed upstream!
  • Optimize DAG-CBOR encoding for arrays and maps by @MarshalX in #88
  • Optimize CAR decoding by reducing allocations by @MarshalX in #90
  • Optimize executable size (panic = "abort") by @MarshalX in #86

Other

🏎️ Performance comparison. Before (v3.2.0) VS After (v3.3.0):

image

You can find more benchmark results here: #92

Version 3.2.0

22.10.2025

❗ Possible breaking change for you: dropped support for PyPy 3.9 and PyPy 3.10 (both no longer supported upstream) in #77. CPython is still supported from 3.8 to 3.14.

Version 3.1.1

24.06.2025

Version 3.1.0

20.06.2025

Version 3.0.1

18.02.2025

  • Update PyO3 to 0.23.4 by @MarshalX in #54
  • Specify the dynamic version in pyproject.toml by @fxcoudert in #53
  • Fix stack overflow when decoding deeply nested structures like arrays and maps by @MarshalX in #51

Possible breaking change for you: dropped support for PyPy 3.8

Version 3.0.0

24.10.2024

❗ Breaking changes

🆕 Features

Other

  • Update PyO3 to 0.22.5 by @MarshalX in #50
  • Update decode_car signature, bytes CIDs instead of str in README by @snarfed in #49

Version 2.0.0

07.09.2024

❗ Breaking changes

CID in CAR block keys and CID DAG-CBOR are now in raw byte form instead of stringified one!

  • Encode and decode CID as bytes (major 2) instead of string (major 3) by @MarshalX in #36

⚡ Bunch of new optimizations

🆕 Features

Other

🏎️ Performance comparison. Before (v1.2.3) VS After (v2.0.0):

performance diff
Raw bench results

Before (v1.2.3):

Hello World Decode:
===================
libipld  : 144 ns

Hello World Encode:
===================
libipld  : 150 ns

Realistic Decode Tests:
=======================
canada.json.dagcbor            libipld  : 5.11 ms (197.31 MB/s)
citm_catalog.json.dagcbor      libipld  : 3.43 ms (95.31 MB/s)
github.json.dagcbor            libipld  : 0.17 ms (270.36 MB/s)
twitter.json.dagcbor           libipld  : 2.15 ms (178.96 MB/s)

Realistic Decode Car Tests:
===========================
atproto_repo.car               libipld  : 119.97 ms (154.22 MB/s)

Realistic Encode Tests:
=======================
canada.json.dagcbor            libipld  : 1.19 ms (848.65 MB/s)
citm_catalog.json.dagcbor      libipld  : 1.66 ms (196.42 MB/s)
github.json.dagcbor            libipld  : 0.11 ms (429.15 MB/s)
twitter.json.dagcbor           libipld  : 0.87 ms (443.77 MB/s)

After (v2.0.0):

Hello World Decode:
===================
libipld  : 124 ns

Hello World Encode:
===================
libipld  : 116 ns

Realistic Decode Tests:
=======================
canada.json.dagcbor            libipld  : 4.65 ms (216.57 MB/s)
citm_catalog.json.dagcbor      libipld  : 2.96 ms (110.37 MB/s)
github.json.dagcbor            libipld  : 0.15 ms (311.00 MB/s)
twitter.json.dagcbor           libipld  : 1.75 ms (219.74 MB/s)

Realistic Decode Car Tests:
===========================
atproto_repo.car               libipld  : 92.44 ms (200.13 MB/s)

Realistic Encode Tests:
=======================
canada.json.dagcbor            libipld  : 1.00 ms (1011.47 MB/s)
citm_catalog.json.dagcbor      libipld  : 1.60 ms (203.57 MB/s)
github.json.dagcbor            libipld  : 0.06 ms (733.96 MB/s)
twitter.json.dagcbor           libipld  : 0.56 ms (686.31 MB/s)

Test stand: M1 Pro, Python 3.12.

I highly recommend using Python 3.12. The lower version will be slower.

P.S. Breaking change not only gives a performance boost but also aligns implementations with other languages.

Version 1.2.3

11.03.2024

⚡ Thanks to PGO and new Bound API in pyo3 v0.21 I achieved huge performance boost ones again! The pyo3 team did an awesome work! Many thanks to all contributors! Also, I implemented own CAR reader that gives +11%

🏎️ Performance comparison. Before (v1.2.2) VS After (v1.2.3):

performance diff
Raw bench results

Before (v1.2.2):

Hello World Decode:
===================
libipld  : 275 ns

Hello World Encode:
===================
libipld  : 237 ns

Realistic Decode Tests:
=======================
canada.json.dagcbor            libipld  : 7.51 ms (134.15 MB/s)
citm_catalog.json.dagcbor      libipld  : 4.97 ms (65.71 MB/s)
github.json.dagcbor            libipld  : 0.23 ms (201.43 MB/s)
twitter.json.dagcbor           libipld  : 2.70 ms (142.51 MB/s)

Realistic Decode Car Tests:
===========================
atproto_repo.car               libipld  : 170.09 ms (108.77 MB/s)

Realistic Encode Tests:
=======================
canada.json.dagcbor            libipld  : 1.89 ms (533.01 MB/s)
citm_catalog.json.dagcbor      libipld  : 2.86 ms (114.02 MB/s)
github.json.dagcbor            libipld  : 0.14 ms (335.64 MB/s)
twitter.json.dagcbor           libipld  : 1.07 ms (358.30 MB/s)

After (v1.2.3):

Hello World Decode:
===================
libipld  : 130 ns

Hello World Encode:
===================
libipld  : 143 ns

Realistic Decode Tests:
=======================
canada.json.dagcbor            libipld  : 5.24 ms (192.11 MB/s)
citm_catalog.json.dagcbor      libipld  : 3.49 ms (93.51 MB/s)
github.json.dagcbor            libipld  : 0.18 ms (257.51 MB/s)
twitter.json.dagcbor           libipld  : 2.11 ms (182.31 MB/s)

Realistic Decode Car Tests:
===========================
atproto_repo.car               libipld  : 123.72 ms (149.55 MB/s)

Realistic Encode Tests:
=======================
canada.json.dagcbor            libipld  : 1.22 ms (823.65 MB/s)
citm_catalog.json.dagcbor      libipld  : 1.69 ms (192.80 MB/s)
github.json.dagcbor            libipld  : 0.11 ms (410.94 MB/s)
twitter.json.dagcbor           libipld  : 0.80 ms (481.72 MB/s)

Test stand: M1 Pro, Python 3.12.

I highly recommend using Python 3.12. The lower version will be slower.

Version 1.2.2

02.03.2024

🏎️ Performance comparison

Before (v1.2.1):

canada.json.dagcbor            libipld  : 1.98 ms (508.99 MB/s)
citm_catalog.json.dagcbor      libipld  : 3.96 ms (82.46 MB/s)
twitter.json.dagcbor           libipld  : 1.92 ms (200.10 MB/s)

After (v1.2.2):

canada.json.dagcbor            libipld  : 2.04 ms (493.43 MB/s)
citm_catalog.json.dagcbor      libipld  : 2.76 ms (118.31 MB/s)
twitter.json.dagcbor           libipld  : 1.03 ms (371.93 MB/s)

CodSpeed Performance Report: #18#issuecomment-1974061926

Version 1.2.1

27.02.2024

  • Improve performance of DAG-CBOR encoding by @MarshalX in #10

Encoding results compared to v1.2.0:

  • canada.json.dagcbor x20.6
  • citm_catalog.json.dagcbor x4.7
  • twitter.json.dagcbor x4.1

Version 1.2.0

26.02.2024

  • Add encode_dag_cbor by @MarshalX in #5
  • Add profiling by @MarshalX in #6
  • Improve performance of DAG-CBOR decoding (remove a lot of unnecessary allocs and drops) by @MarshalX in #8

Decoding results compared to v1.1.1:

  • citm_catalog.json.dagcbor x2
  • canada.json.dagcbor x1.64
  • twitter.json.dagcbor x1.29

Decoding results compared to v1.1.0:

  • citm_catalog.json.dagcbor x4.9
  • canada.json.dagcbor x4.6
  • twitter.json.dagcbor x4.37

Version 1.1.1

24.02.2024

  • Add error handling by @MarshalX in #4
  • Improve performance (at least x2 boost for CAR and DAG-CBOR decoding) by @MarshalX in #4
  • Fix CID decoding in DAG-CBOR by @MarshalX in #4

Version 1.1.0

10.01.2024

Version 1.0.1

27.10.2023

Version 1.0.0

13.09.2023

The first stable release

Features

  • Decode CID (decode_cid(str) -> dict)
  • Decode DAG-CBOR (decode_dag_cbor(bytes) -> dict, decode_dag_cbor_multi(bytes) -> list[dict])
  • Decode CAR (decode_car(bytes) -> tuple[dict, dict[str, dict]]). Returns a header and blocks mapped by CID.

by @MarshalX