Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 5d43ab597d9f9ebbc7d345e807e31b84e4c9ed2e
      
https://github.com/WebKit/WebKit/commit/5d43ab597d9f9ebbc7d345e807e31b84e4c9ed2e
  Author: Keith Miller <[email protected]>
  Date:   2026-09-25 (Fri, 25 Sep 2026)

  Changed paths:
    M Source/WTF/benchmarks/LockSpeedTest.cpp
    M Source/WTF/benchmarks/ToyLocks.h
    M Source/WTF/wtf/Condition.h
    M Source/WTF/wtf/LockAlgorithmInlines.h
    M Source/WTF/wtf/Locker.h
    M Source/WTF/wtf/ParkingLot.cpp
    M Source/WTF/wtf/ParkingLot.h
    M Source/WTF/wtf/ReadWriteLock.cpp
    M Source/WTF/wtf/ReadWriteLock.h
    M Tools/TestWebKitAPI/CMakeLists.txt
    M Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
    A Tools/TestWebKitAPI/Tests/WTF/ReadWriteLock.cpp

  Log Message:
  -----------
  Optimize ReadWriteLock
https://bugs.webkit.org/show_bug.cgi?id=325091
rdar://188244976

Reviewed by Yusuke Suzuki.

ReadWriteLock was a Lock plus a Condition protecting a reader count, so every 
read acquire and
release took an exclusive lock, and readers serialized on it even when no 
writer was around.

This replaces it with a phase-fair lock inspired by the PF-T algorithm from 
Brandenburg and Anderson,
"Spin-Based Reader-Writer Synchronization for Multiprocessor Real-Time 
Systems". Readers take a
ticket from an "in" counter and return one to an "out" counter, so an 
uncontended read lock and
unlock is one atomic read-modify-write each to different words. A writer, 
serialized against
other writers by a WTF::Lock, claims the next phase by flipping a phase bit in 
the "in" word, which
holds back readers arriving after it, and then waits only for the readers 
already counted in to
leave. Readers blocked by one writer wait only for that writer, not for writers 
queued behind it.

Unlike PF-T, both sides spin for a bounded time and then park in ParkingLot. 
The last reader to
leave wakes a parked writer; the drain target lives in the same 64-bit word as 
the out count, so
that reader reads it with the same RMW that tells it it was last. The whole 
lock is 16 bytes.

The new ParkingLot::unparkCount(address, count, callback) is what lets a 
releasing writer wake every
parked reader at once. Its callback runs under the bucket lock even when 
nothing was dequeued, which
is what makes clearing the parked bit there race-free. unparkOne is now 
implemented on top of it,
and UnparkResult::didUnparkThread becomes unparkedCount.

The nested ReadLock/WriteLock classes become ReadLockView/WriteLockView, with 
Locker
specializations annotated for thread safety analysis: Locker { lock.read() } 
acquires shared and
Locker { lock.write() } acquires exclusive, and data guarded by the lock is 
recognized as held under
either.

Cells are reads KHz / writes KHz / write-acquire latency usec. So (bigger / 
bigger / smaller) is better.

  config                        ReadWriteLock        PFTLock (paper)       
PFTParkingLock
  ----------------------------  -------------------  --------------------  
-------------------
  4r1w, 100us sleep             20507 / 7.7 / 0.45   20225 / 7.7 / 0.44    
21126 / 7.7 / 0.47
  8r1w, 100us                   10884 / 7.7 / 1.07   11019 / 7.7 / 0.98    
11004 / 7.7 / 1.10
  8r2w, 100us                   10949 / 15.3 / 1.11  11005 / 15.3 / 1.06   
11061 / 15.3 / 1.26
  8r2w, 10us                    10503 / 119 / 1.09   10500 / 119 / 1.00    
10516 / 119 / 1.11
  8r2w, no sleep                 8375 / 540 / 2.81    7091 / 868 / 1.86     
9474 / 234 / 7.34
  8r8w, 100us                   10434 / 60.5 / 2.46  10357 / 60.2 / 2.36   
10417 / 60.5 / 2.75
  8r2w, ~110ns CS, 10us          9841 / 108 / 1.32   10022 / 108 / 1.20     
3970 / 57 / 7.87
  8r2w, ~110ns CS, no sleep      6435 / 448 / 3.17    5265 / 651 / 2.36     
6822 / 61 / 25.5
  8r2w, ~570ns CS, 10us          5972 / 98 / 1.14     6024 / 99 / 1.08      
1331 / 32 / 22.8
  8r2w, ~570ns CS, no sleep      2018 / 186 / 6.57    1655 / 207 / 6.15     
2185 / 38 / 32.8
  28r4w, 100us (oversubscribed)  7683 / 0.6 / 4146    1065 / 0.0 / 166629   
7519 / 0.6 / 4068

  config                        CountedRWLock        std::shared_mutex
  ----------------------------  -------------------  -------------------
  4r1w, 100us sleep             13087 / 7.7 / 0.30    4312 / 7.2 / 5.25
  8r1w, 100us                    4468 / 7.7 / 0.71    4930 / 6.5 / 16.7
  8r2w, 100us                    4341 / 15.3 / 0.82   4329 / 12.9 / 17.7
  8r2w, 10us                     4819 / 122 / 0.83    2247 / 39.8 / 24.9
  8r2w, no sleep                  312 / 51 / 29.3     1422 / 404 / 3.69
  8r8w, 100us                    4409 / 60.9 / 1.07   2077 / 46.9 / 32.6
  8r2w, ~110ns CS, 10us          5312 / 116 / 0.99     861 / 35 / 31.6
  8r2w, ~110ns CS, no sleep       302 / 46 / 32.4      454 / 125 / 11.8
  8r2w, ~570ns CS, 10us          2735 / 70 / 6.00      527 / 31 / 34.9
  8r2w, ~570ns CS, no sleep       276 / 37 / 40.0      170 / 73 / 18.0
  28r4w, 100us (oversubscribed)  2154 / 5.2 / 73.3    3823 / 10.8 / 131

Measured with LockSpeedTest on a 16-logical-CPU Mac M4 Pro.

Against std::shared_mutex this reads 2.2-14x as fast in every configuration, 
with up to 28x lower
write-acquire latency whenever the lock is not oversubscribed. Against PFTLock 
it matches read
throughput with an empty critical section, and beats it on writes everywhere: 
PFTLock's writers
park after a short spin and pay a full hand-off on every acquisition, so at 
realistic critical
section lengths this lock sustains 2-4.6x the writes at a fifth to a 
seventeenth of the latency.

When readers far outnumber cores, writers starve waiting on readers that were 
preempted inside
their critical section. That is inherent to phase-fair locks, and PFTLock shows 
it too, but it is
worse here; locks that hold new readers back behind waiting writers do much 
better in that one
configuration. That said, we generally try to avoid oversubscribing the cores 
on the system
anyway so it's not clear how important that case is.

Test: Tools/TestWebKitAPI/Tests/WTF/ReadWriteLock.cpp
Canonical link: https://commits.webkit.org/321928@main



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to