Skip to content

Conversation

s0l0ist
Copy link

@s0l0ist s0l0ist commented Aug 6, 2025

Replaces the ulid crate with ferroid, a drop-in alternative with identical behavior in this context, but better performance for encode/decode.

Also saves an allocation (not that it is critical here).

There's no functional difference here, but ferroid provides a more modern foundation for ULID.

Copy link

netlify bot commented Aug 6, 2025

Deploy Preview for testcontainers-rust ready!

Name Link
🔨 Latest commit 059c5ca
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-rust/deploys/689514935390130008a8e8e6
😎 Deploy Preview https://deploy-preview-829--testcontainers-rust.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@DDtKey DDtKey changed the title Replace ulid with ferroid's ULID for better performance refactor: replace ulid with ferroid's ULID for better performance Aug 6, 2025
@mervyn-mccreight mervyn-mccreight changed the title refactor: replace ulid with ferroid's ULID for better performance perf: replace ulid with ferroid's ULID for better performance Aug 7, 2025
@DDtKey
Copy link
Contributor

DDtKey commented Aug 10, 2025

Thank you for the contribution!

I generally would be happy to accept more performant & modern version

But just to understand - are there any benchmarks to compare ferroid vs ulid? And would be even better to compare to uuid v7

I also see some deviations from ULID spec are mentioned in the readme. Based on the description it looks safe enough (at quick glance) and there shouldn't be overflow bugs, but probably it's a sign of including this change into the next major release, not minor/patch.

@s0l0ist
Copy link
Author

s0l0ist commented Aug 11, 2025

Full disclosure, I'm the author of ferroid. I ran some basic benchmarking for base32 encode/decode here, but I'd say there probably needs to be more.

ferrroid's base32 algo is marginally faster than ulid-rs and slightly slower than uuid (v7) at encode/decode mainly because uuid uses hex-encoding which is less work than base32.

Here's some benchmarks:

uuid (--features v7):

test encode_hyphen ... bench:           5.75 ns/iter (+/- 0.04)
test encode_simple ... bench:           7.74 ns/iter (+/- 0.07)
test encode_urn    ... bench:           6.07 ns/iter (+/- 0.05)
test hyphenated    ... bench:          19.22 ns/iter (+/- 0.08)
test simple        ... bench:          17.94 ns/iter (+/- 1.22)
test urn           ... bench:          20.29 ns/iter (+/- 0.60)
...
test parse_invalid_character ... bench:          31.60 ns/iter (+/- 0.76)
test parse_invalid_group_len ... bench:          50.61 ns/iter (+/- 2.31)
test parse_invalid_groups    ... bench:          41.56 ns/iter (+/- 0.40)
test parse_invalid_len       ... bench:          40.27 ns/iter (+/- 0.30)
test parse_nil               ... bench:          13.17 ns/iter (+/- 0.06)
test parse_nil_hyphenated    ... bench:          14.11 ns/iter (+/- 0.13)
test parse_random            ... bench:          13.24 ns/iter (+/- 0.10)
test parse_random_hyphenated ... bench:          14.06 ns/iter (+/- 0.43)
test parse_urn               ... bench:          14.92 ns/iter (+/- 0.33)
...
test new_v7_context                      ... bench:          51.70 ns/iter (+/- 0.30)
test new_v7_context_additional_precision ... bench:          62.98 ns/iter (+/- 0.34)
test new_v7_no_context                   ... bench:          53.82 ns/iter (+/- 1.21)
test now_v7                              ... bench:          54.22 ns/iter (+/- 0.74)
test v7_raw                              ... bench:          29.66 ns/iter (+/- 0.23)

ulid-rs:

test bench_from_string        ... bench:          10 ns/iter (+/- 0)
test bench_from_time          ... bench:          20 ns/iter (+/- 0)
test bench_generator_generate ... bench:          26 ns/iter (+/- 0)
test bench_new                ... bench:          42 ns/iter (+/- 0)
test bench_to_str             ... bench:           9 ns/iter (+/- 0)
test bench_to_string          ... bench:          25 ns/iter (+/- 0)

ferroid:

ULID/from_raw           
                        time:   [310.22 ps 310.34 ps 310.56 ps]
                        thrpt:  [3.2200 Gelem/s 3.2223 Gelem/s 3.2235 Gelem/s
ULID/from_timestamp
                        time:   [21.092 ns 21.131 ns 21.181 ns]
                        thrpt:  [47.212 Melem/s 47.324 Melem/s 47.411 Melem/s]
ULID/from_datetime
                        time:   [42.036 ns 42.042 ns 42.048 ns]
                        thrpt:  [23.782 Melem/s 23.786 Melem/s 23.789 Melem/s]

Both ulid-rs and ferroid are faster at ID generation coming in at ~42 ns each. I had to modify the benchmark code in the ulid-rs to use black_box to get the same performance (otherwise it incorrectly reports 40ns for bench_new).

uuidv7 performance is almost certainly related to the bookkeeping behind the shared context and how it internally computes when to reseed (every 1 millisecond) vs deferring reseeding until the thread local in the rand crate exceeds a total generated count.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants