Skip to content

Reduce the cost of upgrading a held UpgradableRwLock read guard #404

Open

Description

@tisonkun

With no competing reader or writer, acquiring an upgradable read guard and promoting it to a write guard took 293 ms versus 155 ms for async-lock over 8.3 million rounds. Both implementations upgrade while continuously holding access. Ordinary uncontended reads are a useful control and were already close in the peer measurements.

Apple M4 Max, macOS arm64, rustc 1.101.0-nightly. Each cell gives rank, elapsed time relative to Asyncband and the whole-batch median over 10 interleaved runs. Lower is faster. Supplemental measurements

Scenario Fixed rounds Asyncband async-lock
Acquire an upgradable read guard and promote it 8,300,000 #2, 1.00× (293 ms) #1, 0.53× (155 ms)

upgradable_read acquires a reservation and one data permit. upgrade requests the remaining data permits before converting the guard. In a separate run, changing only the internal lock reduced Asyncband from 311 to 270 ms, with async-lock at 158 ms. The number of permit transitions on the uncontended path is a candidate for investigation.

First contribution

Time acquisition and promotion separately on the same lock. Compare the uncontended path with a reader still present and with a writer waiting for the reservation. Preserve the documented ordering of the two queues, continuous access during upgrade, and cleanup of reservations and assigned permits on cancellation.

To run the supplemental peer_checks command below, save and run peer-checks.rb from the linked comment in a disposable checkout of 6102089. The benchmark and library sources match 36ba6f2. Generator and instructions

cargo x bench --bench primitives -- upgradable_read_then_upgrade --repeat 10 --raw
cargo x bench --bench peer_checks -- rwlock/upgrade --repeat 10 --raw

Performance tracking issue #396

Activity

  1. ismaeelakin commented on Nov 3, 2026

    @ismaeelakin

    I ran the existing lock benchmarks again on my Windows 11 laptop (Intel Core i5-6300U, rustc 1.99.1, commit 36ba6f27c5003524c10fd348720c34825122ca87). The immediate try_upgrade case came in at 2,617 ms median over 8.3 million rounds (five runs, 2,405–3,113 ms); acquisition and reuse alone was 2,079 ms (1,812–2,106 ms). The spread is wide enough that I would not treat their difference as a reliable promotion cost yet.

    With one reader held during upgrade, the queued-upgrade row measured 1,461 ms per million rounds (five runs); with four readers it was 1,920 ms. The 16-reader row was especially noisy: 978 ms median over 300,000 rounds, three runs (909–1,952 ms). These timings include setting up and releasing readers, so they are context rather than isolated upgrade timings.

    Commands:

    • cargo x bench --bench primitives -- upgradable_read_then_try_upgrade --rounds 8300000 --repeat 5 --raw
    • cargo x bench --bench primitives -- upgradable_read_reuse --rounds 8300000 --repeat 5 --raw
    • cargo x bench --bench primitives -- readers_then_queued_upgrade --rounds 1000000 --repeat 5 --raw (one and four readers)
    • cargo x bench --bench primitives -- readers_then_queued_upgrade/16 --rounds 300000 --repeat 3 --raw

    I do not have a performance change to propose yet. The next useful step is a stable stage-level measurement.

  2. ismaeelakin commented on Nov 4, 2026

    @ismaeelakin

    I have updated #405 with the requested queue cases and support for the benchmark smoke run.

    I ran cargo x bench --bench upgradable_stages -- --rounds 1000000 --repeat 5 on Windows 11, an Intel Core i5-6300U, and stable rustc 1.99.1. The medians were:

    Case Median per round
    Upgradable acquisition 368 ns
    Promotion 439 ns
    Acquisition with an ordinary reader held 345 ns
    Promotion with 1 reader held 948 ns
    Promotion with 4 readers held 1,479 ns
    Promotion with 16 readers held 2,611 ns
    Promotion with a writer queued 508 ns
    Upgradable acquisition behind a queued writer 1,058 ns

    The batches were noisy: for example, the acquisition runs ranged from 344 to 594 ms, and the 16-reader promotion runs from 2,358 to 4,608 ms. The reader-promotion figures include dropping the read guards and waking the upgrader; the queued-acquisition figure includes the writer's handoff. These are diagnostic measurements on this laptop, not a cross-library comparison or a performance claim. The benchmark manually polls futures with a non-scheduling waker, so the timings exclude executor scheduling overhead.

    The full workspace tests, project lint, and cargo x bench -- --test all pass locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions