Skip to content

perf: sharded OnceTable - #225

Open
Huliiiiii wants to merge 3 commits into
apache:mainfrom
Huliiiiii:issue-200
Open

perf: sharded OnceTable#225
Huliiiiii wants to merge 3 commits into
apache:mainfrom
Huliiiiii:issue-200

Conversation

@Huliiiiii

Copy link
Copy Markdown

Alternative of #205

Improvements

CPU: AMD Ryzen 9 9950X
OS: NixOS 25.11

Thread: 8

Benchmark (entry size) Baseline #205 This PR (Mutex) This PR vs. #205 parking_lot::RwLock RwLock vs. #205 RwLock vs. This PR
once_map::contended_compute_coalesced 7.027 µs 6.650 µs 6.906 µs 3.7% slower 6.666 µs 0.2% slower 3.5% faster
once_map::contended_compute_hit_same_key 964 ns 296 ns 1.113 µs 73.4% slower 312 ns 5.1% slower 3.6× faster
once_map::contended_compute_hit_disjoint (64) 1.114 µs 357 ns 110 ns 3.2× faster 103 ns 3.5× faster 6.3% faster
once_map::contended_compute_hit_disjoint (1024) 1.156 µs 296 ns 94 ns 3.1× faster 103 ns 2.9× faster 8.4% slower
once_map::contended_compute_miss_churn (64) 3.552 µs 4.979 µs 208 ns 23.9× faster 223 ns 22.4× faster 6.6% slower
once_map::contended_compute_miss_churn (1024) 3.542 µs 4.867 µs 223 ns 21.8× faster 231 ns 21.1× faster 3.4% slower
once_map::contended_compute_mixed (64) 2.589 µs 3.305 µs 163 ns 20.3× faster 166 ns 19.9× faster 1.9% slower
once_map::contended_compute_mixed (1024) 2.513 µs 3.200 µs 169 ns 18.9× faster 168 ns 19.0× faster 0.4% faster
once_map::contended_get_hit_same_key 852 ns 322 ns 960 ns 66.5% slower 638 ns 49.5% slower 1.5× faster
once_map::contended_get_hit_disjoint (64) 935 ns 464 ns 89 ns 5.2× faster 124 ns 3.8× faster 28.0% slower
once_map::contended_get_hit_disjoint (1024) 963 ns 461 ns 65 ns 7.1× faster 70 ns 6.6× faster 7.4% slower
singleflight::contended_work_coalesced 6.746 µs 6.937 µs 7.351 µs 5.6% slower 6.594 µs 4.9% faster 10.3% faster
singleflight::contended_work_same_key 1.881 µs 2.167 µs 1.719 µs 1.3× faster 1.790 µs 1.2× faster 4.0% slower
singleflight::contended_work_disjoint_churn 3.533 µs 4.780 µs 217 ns 22.0× faster 221 ns 21.7× faster 1.6% slower

Thread: 32

Benchmark (entry size) Baseline #205 This PR (Mutex) This PR vs. #205 parking_lot::RwLock RwLock vs. #205 RwLock vs. This PR
once_map::contended_compute_coalesced 28.900 µs 31.010 µs 29.900 µs 3.6% faster 46.520 µs 33.3% slower 35.7% slower
once_map::contended_compute_hit_same_key 3.915 µs 2.576 µs 6.607 µs 61.0% slower 2.097 µs 18.6% faster 3.2× faster
once_map::contended_compute_hit_disjoint (64) 5.513 µs 3.004 µs 882 ns 3.4× faster 278 ns 10.8× faster 3.2× faster
once_map::contended_compute_hit_disjoint (1024) 5.981 µs 2.869 µs 334 ns 8.6× faster 243 ns 11.8× faster 1.4× faster
once_map::contended_compute_miss_churn (64) 29.290 µs 42.010 µs 948 ns 44.3× faster 1.246 µs 33.7× faster 23.9% slower
once_map::contended_compute_miss_churn (1024) 26.960 µs 38.790 µs 988 ns 39.3× faster 1.257 µs 30.9× faster 21.4% slower
once_map::contended_compute_mixed (64) 19.780 µs 25.620 µs 822 ns 31.2× faster 872 ns 29.4× faster 5.7% slower
once_map::contended_compute_mixed (1024) 19.140 µs 24.010 µs 721 ns 33.3× faster 780 ns 30.8× faster 7.6% slower
once_map::contended_get_hit_same_key 3.428 µs 2.098 µs 5.190 µs 59.6% slower 2.954 µs 29.0% slower 1.8× faster
once_map::contended_get_hit_disjoint (64) 4.849 µs 2.755 µs 623 ns 4.4× faster 319 ns 8.6× faster 2.0× faster
once_map::contended_get_hit_disjoint (1024) 4.896 µs 2.810 µs 325 ns 8.6× faster 234 ns 12.0× faster 1.4× faster
singleflight::contended_work_coalesced 30.750 µs 28.620 µs 35.160 µs 18.6% slower 32.200 µs 11.1% slower 8.4% faster
singleflight::contended_work_same_key 12.920 µs 13.960 µs 13.120 µs 6.0% faster 17.480 µs 20.1% slower 24.9% slower
singleflight::contended_work_disjoint_churn 28.160 µs 39.170 µs 916 ns 42.8× faster 1.274 µs 30.7× faster 28.1% slower

@aisk

aisk commented Aug 27, 2026

Copy link
Copy Markdown

Verified on bare-metal Linux: AMD Ryzen 7 5700X (8C/16T), NixOS 26.11, kernel 7.2.0, rustc 1.96.0, t=8, --sample-count 100 --sample-size 10000, interleaved rounds ×3 against base (c461305) and #205's head (6c55ca4). Values are medians of the 3 round-medians; per-round variance was <5% throughout, including the churn scenarios.

scenario base this PR #205 (6c55ca4)
compute_hit_same_key 809.1 ns 810.3 ns 165.9 ns
get_hit_same_key 547.9 ns 599.0 ns 343.9 ns
compute_hit_disjoint (64) 908.3 ns 100.9 ns 168.5 ns
compute_hit_disjoint (1024) 913.0 ns 96.3 ns 174.7 ns
get_hit_disjoint (64) 715.6 ns 84.2 ns 518.9 ns
get_hit_disjoint (1024) 727.3 ns 78.8 ns 530.8 ns
compute_miss_churn (64) 3.06 µs 223.6 ns 4.05 µs
compute_miss_churn (1024) 2.91 µs 232.5 ns 3.92 µs
compute_mixed (64) 2.21 µs 175.1 ns 2.63 µs
compute_mixed (1024) 2.16 µs 179.0 ns 2.54 µs
compute_coalesced 18.41 µs 18.53 µs 18.47 µs
work_same_key 2.07 µs 1.80 µs 2.14 µs
work_disjoint_churn 3.02 µs 233.4 ns 3.74 µs
work_coalesced 18.58 µs 18.47 µs 18.67 µs
  • The headline wins reproduce: 8.5–9.5× on disjoint hits, 12–13.7× on miss_churn/mixed/work_disjoint_churn (below the 16–24× on the 9950X, still decisive).
  • The same-key penalty is milder here: get_hit_same_key −9% vs base, compute_hit_same_key on par, work_same_key +15%.
  • Same-key hits are the one weak spot vs perf: take the keyed table read lock on hits and duplicate waiters #205: every hit takes the shard mutex exclusively, so same-key traffic serializes on one shard — 810 vs 166 ns on compute hits (4.9×), 599 vs 344 ns on get (1.7×).
  • Question: the branch only contains the Mutex-shard variant; the parking_lot::RwLock column from the table doesn't seem to be in the code. Is that variant pushed anywhere?

The two approaches look complementary: sharding covers disjoint/churn, a shared reader path plus the hit shortcut from 6c55ca4 covers same-key. A per-shard reader path on top of this PR should cover every scenario — happy to help with that.

@Huliiiiii

Huliiiiii commented Aug 27, 2026

Copy link
Copy Markdown
Author

Refactor the storage of OnceMap, add a concurrent index for Ready values.
This makes the current implementation significantly superior to other solutions when using two or more threads.

Also fixed some issues in benchmarks, there is still too much variance in the results.

Threads Benchmark Range (Span)
1 compute_hit_disjoint (64) Current: 30.76–33.92 (10.2%)
1 compute_hit_disjoint (1024) Current: 31.03–34.44 (10.8%)
2 compute_hit_disjoint (64) Mutex: 40.23–50.19 (24.6%); RwLock: 36.16–67.90 (85.0%)
2 compute_hit_disjoint (1024) Mutex: 48.72–53.93 (10.4%)
2 get_hit_disjoint (64) Mutex: 24.17–48.20 (98.0%); RwLock: 24.40–43.33 (77.0%)
2 get_hit_same_key Mutex: 115.2–137.2 (16.5%)
8 compute_hit_disjoint (64) Mutex: 56.25–70.49 (23.8%); RwLock: 52.71–69.58 (28.0%)
8 compute_hit_disjoint (1024) Mutex: 70.51–78.34 (11.1%)
8 compute_hit_same_key Mutex: 972.3–1126 (14.1%); RwLock: 304.0–374.5 (22.1%)
8 compute_miss_churn (1024) Mutex: 222.5–251.6 (12.8%); Current: 172.0–208.1 (20.9%)
8 compute_mixed (64) Current: 98.62–110.0 (11.4%)
8 compute_mixed (1024) Mutex: 160.7–208.3 (29.5%)
8 get_hit_disjoint (64) Mutex: 37.26–54.95 (41.5%); RwLock: 36.37–46.83 (24.3%)
8 get_hit_same_key RwLock: 625.9–1000 (52.7%)
32 compute_hit_disjoint (64) Mutex: 149.6–212.6 (36.6%); RwLock: 109.9–150.8 (30.5%)
32 compute_hit_disjoint (1024) RwLock: 163.3–181.9 (10.9%)
32 compute_hit_same_key RwLock: 2087–3257 (52.7%)
32 get_hit_disjoint (64) Mutex: 99.28–144.3 (35.1%); RwLock: 93.72–127.0 (33.1%)
32 get_hit_same_key RwLock: 2575–3316 (28.0%)

New Result:

1 thread

Benchmark Mutex vs. baseline RwLock vs. baseline RwLock (non-sharded) vs. baseline Current vs. baseline Current vs. Mutex Current vs. RwLock Current vs. RwLock (non-sharded)
once_map::contended_compute_hit_disjoint (64) 23.8% slower 16.3% slower On par 4.0% slower 16.0% faster 10.6% faster 4.9% slower
once_map::contended_compute_hit_disjoint (1024) 26.2% slower 15.9% slower On par 2.9% slower 23.0% faster 11.3% faster 3.1% slower
once_map::contended_compute_hit_same_key 28.5% slower 22.7% slower 2.4% slower 2.6% faster 32.0% faster 26.0% faster 4.8% faster
once_map::contended_compute_miss_churn (64) 3.9% slower On par On par 15.0% slower 10.7% slower 13.2% slower 15.9% slower
once_map::contended_compute_miss_churn (1024) 5.9% slower 5.5% slower 3.4% faster 14.3% slower 7.9% slower 8.4% slower 18.3% slower
once_map::contended_compute_mixed (64) 9.0% slower 6.7% slower On par 12.5% slower 3.2% slower 5.4% slower 11.3% slower
once_map::contended_compute_mixed (1024) 14.2% slower 10.6% slower On par 13.1% slower On par 2.3% slower 13.0% slower
once_map::contended_get_hit_disjoint (64) 30.8% slower 33.0% slower 5.7% faster 6.9% faster 41.0% faster 43.0% faster On par
once_map::contended_get_hit_disjoint (1024) 31.3% slower 38.2% slower 5.7% faster 6.9% faster 41.0% faster 48.0% faster On par
once_map::contended_get_hit_same_key 39.1% slower 55.2% slower 8.7% faster 7.9% faster 51.0% faster 68.0% faster On par

2 threads

Benchmark Mutex vs. baseline RwLock vs. baseline RwLock (non-sharded) vs. baseline Current vs. baseline Current vs. Mutex Current vs. RwLock
once_map::contended_compute_hit_disjoint (64) 3.06× faster 3.23× faster 3.2% slower 3.74× faster 22.0% faster 13.6% faster
once_map::contended_compute_hit_disjoint (1024) 2.54× faster 2.72× faster 2.2% slower 3.94× faster 56.0% faster 45.0% faster
once_map::contended_compute_hit_same_key 22.2% slower 5.1% slower 3.3% faster 4.29× faster 5.24× faster 4.51× faster
once_map::contended_compute_miss_churn (64) 3.41× faster 3.41× faster 4.8% slower 3.31× faster 3.1% slower 2.9% slower
once_map::contended_compute_miss_churn (1024) 3.08× faster 3.09× faster 3.3% slower 3.06× faster On par On par
once_map::contended_compute_mixed (64) 3.38× faster 3.45× faster 6.6% slower 3.58× faster 5.5% faster 3.6% faster
once_map::contended_compute_mixed (1024) 2.97× faster 3.02× faster On par 3.47× faster 14.5% faster 13.0% faster
once_map::contended_get_hit_disjoint (64) 4.87× faster 4.62× faster 64.0% faster 6.81× faster 40.0% faster 47.0% faster
once_map::contended_get_hit_disjoint (1024) 3.67× faster 3.71× faster 54.0% faster 6.52× faster 77.0% faster 76.0% faster
once_map::contended_get_hit_same_key 32.0% slower 4.9% slower 53.0% faster 6.28× faster 8.29× faster 6.59× faster

8 threads

Benchmark Mutex vs. baseline RwLock vs. baseline Current vs. baseline Current vs. Mutex Current vs. RwLock
once_map::contended_compute_hit_disjoint (64) 18.66× faster 17.56× faster 31.49× faster 69.0% faster 79.0% faster
once_map::contended_compute_hit_disjoint (1024) 14.42× faster 14.90× faster 31.74× faster 2.20× faster 2.13× faster
once_map::contended_compute_hit_same_key 20.9% slower 12.8% slower 25.82× faster 31.21× faster 29.13× faster
once_map::contended_compute_miss_churn (64) 16.01× faster 16.46× faster 17.05× faster 6.1% faster 3.5% faster
once_map::contended_compute_miss_churn (1024) 15.61× faster 13.91× faster 16.33× faster 4.4% faster 14.8% faster
once_map::contended_compute_mixed (64) 14.23× faster 18.12× faster 19.88× faster 40.0% faster 8.8% faster
once_map::contended_compute_mixed (1024) 13.63× faster 13.08× faster 20.81× faster 53.0% faster 59.0% faster
once_map::contended_get_hit_disjoint (64) 18.56× faster 22.29× faster 51.48× faster 2.77× faster 2.31× faster
once_map::contended_get_hit_disjoint (1024) 17.19× faster 17.26× faster 52.50× faster 3.05× faster 3.04× faster
once_map::contended_get_hit_same_key 24.2% slower 38.0% faster 48.16× faster 59.82× faster 34.90× faster

32 threads

Benchmark Mutex vs. baseline RwLock vs. baseline Current vs. baseline Current vs. Mutex Current vs. RwLock
once_map::contended_compute_hit_disjoint (64) 35.88× faster 36.90× faster 119.78× faster 3.34× faster 3.25× faster
once_map::contended_compute_hit_disjoint (1024) 23.48× faster 20.96× faster 136.17× faster 5.80× faster 6.50× faster
once_map::contended_compute_hit_same_key 46.0% slower 36.6% slower 114.70× faster 167.46× faster 156.64× faster
once_map::contended_compute_miss_churn (64) 28.91× faster 29.47× faster 35.27× faster 22.0% faster 16.5% faster
once_map::contended_compute_miss_churn (1024) 29.01× faster 28.94× faster 36.95× faster 27.0% faster 28.0% faster
once_map::contended_compute_mixed (64) 29.71× faster 29.23× faster 51.15× faster 72.0% faster 75.0% faster
once_map::contended_compute_mixed (1024) 26.58× faster 27.89× faster 50.45× faster 90.0% faster 81.0% faster
once_map::contended_get_hit_disjoint (64) 34.82× faster 39.42× faster 174.31× faster 5.01× faster 4.42× faster
once_map::contended_get_hit_disjoint (1024) 18.09× faster 28.76× faster 179.66× faster 9.93× faster 6.25× faster
once_map::contended_get_hit_same_key 37.8% slower 43.0% faster 153.96× faster 212.13× faster 107.46× faster

@Huliiiiii
Huliiiiii force-pushed the issue-200 branch 7 times, most recently from d7108aa to 0bfec50 Compare August 27, 2026 23:12
@Huliiiiii
Huliiiiii force-pushed the issue-200 branch 2 times, most recently from 984b5df to 2f3d7d4 Compare August 27, 2026 23:39
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