-
-
Save fantix/647ca9b9e002cbd89c1af4525d8923ee to your computer and use it in GitHub Desktop.
Test Rust shared resource guards
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[macro_use] | |
extern crate lazy_static; | |
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; | |
use std::thread::sleep; | |
use std::time::Duration; | |
static READERS: u64 = 100; | |
static WRITERS: u64 = 2; | |
static RUNNING: AtomicBool = AtomicBool::new(true); | |
static READ_COUNT: AtomicU64 = AtomicU64::new(0); | |
const PROGRESS_EVERY: u64 = 50000; | |
static SLEEP: Duration = Duration::from_secs(16); | |
lazy_static! { | |
static ref S_MUTEX: std::sync::Mutex<u64> = std::sync::Mutex::new(0); | |
static ref S_RWLOCK: std::sync::RwLock<u64> = std::sync::RwLock::new(0); | |
static ref P_MUTEX: parking_lot::Mutex<u64> = parking_lot::Mutex::new(0); | |
static ref P_RWLOCK: parking_lot::RwLock<u64> = parking_lot::RwLock::new(0); | |
} | |
macro_rules! print_stat { | |
($title: expr, $read_count: expr, $write_count: expr) => { | |
println!( | |
"{} spent: {} reads, {} writes", | |
$title, $read_count, $write_count, | |
); | |
}; | |
} | |
fn bench_parking_lot_rwlock() -> (u64, u64) { | |
let writers = (0..WRITERS) | |
.map(|i| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let mut a = P_RWLOCK.write(); | |
*a += 1; | |
*a += 2; | |
*a += 3; | |
*a += 4; | |
for _ in 0..100 { | |
let _a = 123; | |
} | |
} | |
println!("write {} done", i); | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
let readers = (0..READERS) | |
.map(|n| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let a = *P_RWLOCK.read(); | |
let i = READ_COUNT.fetch_add(1, Ordering::Relaxed); | |
if i % PROGRESS_EVERY == 0 { | |
println!("read {} @ {} = {}", n, i, a); | |
} | |
} | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
sleep(SLEEP); | |
RUNNING.store(false, Ordering::SeqCst); | |
let rv = (READ_COUNT.swap(0, Ordering::SeqCst), *P_RWLOCK.read()); | |
readers | |
.into_iter() | |
.chain(writers.into_iter()) | |
.map(|h| h.join().unwrap()) | |
.for_each(drop); | |
rv | |
} | |
fn bench_parking_lot_mutex() -> (u64, u64) { | |
let writers = (0..WRITERS) | |
.map(|i| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let mut a = P_MUTEX.lock(); | |
*a += 1; | |
*a += 2; | |
*a += 3; | |
*a += 4; | |
for _ in 0..100 { | |
let _a = 123; | |
} | |
} | |
println!("write {} done", i); | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
let readers = (0..READERS) | |
.map(|n| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let a = *P_MUTEX.lock(); | |
let i = READ_COUNT.fetch_add(1, Ordering::Relaxed); | |
if i % PROGRESS_EVERY == 0 { | |
println!("read {} @ {} = {}", n, i, a); | |
} | |
} | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
sleep(SLEEP); | |
RUNNING.store(false, Ordering::SeqCst); | |
let rv = (READ_COUNT.swap(0, Ordering::SeqCst), *P_MUTEX.lock()); | |
readers | |
.into_iter() | |
.chain(writers.into_iter()) | |
.map(|h| h.join().unwrap()) | |
.for_each(drop); | |
rv | |
} | |
fn bench_sync_rwlock() -> (u64, u64) { | |
let writers = (0..WRITERS) | |
.map(|i| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let mut a = S_RWLOCK.write().unwrap(); | |
*a += 1; | |
*a += 2; | |
*a += 3; | |
*a += 4; | |
for _ in 0..100 { | |
let _a = 123; | |
} | |
} | |
println!("write {} done", i); | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
let readers = (0..READERS) | |
.map(|n| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let a = *S_RWLOCK.read().unwrap(); | |
let i = READ_COUNT.fetch_add(1, Ordering::Relaxed); | |
if i % PROGRESS_EVERY == 0 { | |
println!("read {} @ {} = {}", n, i, a); | |
} | |
} | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
sleep(SLEEP); | |
RUNNING.store(false, Ordering::SeqCst); | |
let rv = ( | |
READ_COUNT.swap(0, Ordering::SeqCst), | |
*S_RWLOCK.read().unwrap(), | |
); | |
readers | |
.into_iter() | |
.chain(writers.into_iter()) | |
.map(|h| h.join().unwrap()) | |
.for_each(drop); | |
rv | |
} | |
fn bench_sync_mutex() -> (u64, u64) { | |
let writers = (0..WRITERS) | |
.map(|i| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let mut a = S_MUTEX.lock().unwrap(); | |
*a += 1; | |
*a += 2; | |
*a += 3; | |
*a += 4; | |
for _ in 0..100 { | |
let _a = 123; | |
} | |
} | |
println!("write {} done", i); | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
let readers = (0..READERS) | |
.map(|n| { | |
std::thread::spawn(move || { | |
while RUNNING.load(Ordering::Relaxed) { | |
let a = *S_MUTEX.lock().unwrap(); | |
let i = READ_COUNT.fetch_add(1, Ordering::Relaxed); | |
if i % PROGRESS_EVERY == 0 { | |
println!("read {} @ {} = {}", n, i, a); | |
} | |
} | |
}) | |
}) | |
.collect::<Vec<_>>(); | |
sleep(SLEEP); | |
RUNNING.store(false, Ordering::SeqCst); | |
let rv = ( | |
READ_COUNT.swap(0, Ordering::SeqCst), | |
*S_MUTEX.lock().unwrap(), | |
); | |
readers | |
.into_iter() | |
.chain(writers.into_iter()) | |
.map(|h| h.join().unwrap()) | |
.for_each(drop); | |
rv | |
} | |
fn main() -> Result<(), Box<dyn std::error::Error>> { | |
println!("PARKING_LOT RWLOCK ==============================="); | |
let (p_rwlock_r, p_rwlock_w) = bench_parking_lot_rwlock(); | |
RUNNING.store(true, Ordering::SeqCst); | |
println!("PARKING_LOT MUTEX ================================"); | |
let (p_mutex_r, p_mutex_w) = bench_parking_lot_mutex(); | |
RUNNING.store(true, Ordering::SeqCst); | |
println!("SYNC RWLOCK ======================================"); | |
let (s_rwlock_r, s_rwlock_w) = bench_sync_rwlock(); | |
RUNNING.store(true, Ordering::SeqCst); | |
println!("SYNC MUTEX ======================================="); | |
let (s_mutex_r, s_mutex_w) = bench_sync_mutex(); | |
print_stat!("PARKING_LOT RWLOCK", p_rwlock_r, p_rwlock_w); | |
print_stat!("PARKING_LOT MUTEX", p_mutex_r, p_mutex_w); | |
print_stat!("SYNC RWLOCK", s_rwlock_r, s_rwlock_w); | |
print_stat!("SYNC MUTEX", s_mutex_r, s_mutex_w); | |
Ok(()) | |
} |
100 readers doing 50,000,000 reads, 2 free writers (previous version of code):
Result on 2020 MBP with 2.3GHz 4-core i7:
PARKING_LOT RWLOCK spent: 10.74906391 sec, 4,700,740 writes
PARKING_LOT MUTEX spent: 65.448203744 sec, 19,120,750 writes
SYNC RWLOCK spent: 33.23635836 sec, 3,378,590 writes
SYNC MUTEX spent: 33.950512621 sec, 5,530,290 writes
Result on 12-core Ryzen 9 5900X:
PARKING_LOT RWLOCK spent: 11.403740884 sec, 469,000 writes
PARKING_LOT MUTEX spent: 108.797817425 sec, 36,626,330 writes
SYNC RWLOCK spent: 5.541838849 sec, 2,021,870 writes
SYNC MUTEX spent: 14.064283695 sec, 6,142,770 writes
Findings:
parking_lot::RwLock
seems to have better performance thanstd::sync::RwLock
with lower contention (4-core, 11s vs 33s, 4.7M writes vs 3.4M)parking_lot::RwLock
is stable with time to read the same number of times under different contention situations (~11s, but writes degraded from 4.7M to 0.5M with higher contention of 12-core)parking_lot::Mutex
is not suitable for this scenario, CPU is not fullstd::sync::RwLock
andstd::sync::Mutex
are similarly "slow" with lower contention (4-core, 33-34s, 3M-5M writes)- Under high contention (12-core),
std::sync::RwLock
is 2x faster thanparking_lot::RwLock
(5.5s vs 11.4s, 2M writes vs 0.5M writes) std::sync::RwLock
did 9M/s reads and 0.36M/s writes, whilestd::sync::Mutex
did 3.6M/s reads and 0.44M/s writes with 12-cores.
std::sync::RwLock
is the winner in this test.
16s, 100 free readers + 2 free writers
Result on 2020 MBP with 2.3GHz 4-core i7:
PARKING_LOT RWLOCK: 67,874,030 reads, 6,358,520 writes
PARKING_LOT MUTEX: 11,822,910 reads, 4,041,520 writes
SYNC RWLOCK: 23,697,876 reads, 1,588,900 writes
SYNC MUTEX: 13,482,674 reads, 1,534,450 writes
Result on 12-core Ryzen 9 5900X:
PARKING_LOT RWLOCK: 70,328,321 reads, 670,990 writes
PARKING_LOT MUTEX: 7,424,345 reads, 5,456,960 writes
SYNC RWLOCK: 142,979,987 reads, 5,707,880 writes
SYNC MUTEX: 54,569,975 reads, 7,053,940 writes
Findings:
parking_lot
doesn't seem to scale well
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
2 writers doing 500,000 writes, 100 free readers (previous revision of code):
Result on 2020 MBP with 2.3GHz 4-core i7:
Result on 12-core Ryzen 9 5900X: