前回の記事でasync/awaitの書き方を複数パターンを考えてみましたが今度はmutex系のパターンを試してみます。
ロックして排他的に読み書きできる機能は幾つかクレートが提供されていますがtokio、標準ライブラリ、parking_lot提供のものを試してみます。
環境はvCPU=6、worker=5000です。
クレート | 概略 |
---|---|
std::sync::Mutex | 標準ライブラリ |
std::sync::RwLock | 標準ライブラリ。write lockのみ使用 |
tokio::sync::Mutex | tokio製 |
tokio::sync::RwLock | tokio製。write lockのみ使用 |
parking_lot::Mutex | 通常よりパフォーマンスがよいらしいmutex |
parking_lot::FairMutex | 通常よりパフォーマンスがよいらしいmutex。スレッドに偏りが少ないらしい |
parking_lot::RwLock | write lockのみ使用 |
std::sync::Mutex
グローバル変数で直接mutex系を使用することは以前はできなかったようですが、ver1.63より可能になったということで試してみます。これはおそらくLinux系のみで他プラットフォームは依然OnceCell等と組み合わせが必要かと思います。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::{Arc, Mutex}; use tokio::fs; // 1.32.0 use tokio::task::JoinSet; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: std::sync::Mutex<Vec<String>> = Mutex::new(Vec::<String>::new()); #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD.lock().unwrap(); lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD.lock().unwrap(); dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(Mutex::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<Mutex<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.lock().unwrap(); lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD.lock().unwrap(); let mut dirs = dirs.lock().unwrap(); lock.append(&mut dirs); } Ok(()) }
std::sync::RwLock
tokio::sync::OnceCellとRwLockの組み合わせです。
Vecを使ったのでwrite lockのみ使用しています。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::Arc; use std::sync::RwLock; use tokio::fs; // 1.32.0 use tokio::sync::OnceCell; use tokio::task::JoinSet; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: tokio::sync::OnceCell<RwLock<Vec<String>>> = OnceCell::const_new(); #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let _ = LIST_FROM_THD.set(RwLock::new(Vec::<String>::new())); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write() .unwrap(); lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write() .unwrap(); dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(RwLock::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<RwLock<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.write().ok().unwrap(); lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write() .unwrap(); let mut dirs = dirs.write().ok().unwrap(); lock.append(&mut dirs); } Ok(()) }
tokio::sync::Mutex
tokio::sync::OnceCellとMutexの組み合わせです。どちらも非同期対応しています。その為メソッドチェーンが長めです。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::Arc; use tokio::fs; // 1.32.0 use tokio::sync::{Mutex, OnceCell}; use tokio::task::JoinSet; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: tokio::sync::OnceCell<tokio::sync::Mutex<Vec<String>>> = OnceCell::const_new(); #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let _ = LIST_FROM_THD.set(Mutex::new(Vec::<String>::new())); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD .get_or_init(|| async move { Mutex::new(Vec::<String>::new()) }) .await .lock() .await; lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD .get_or_init(|| async move { Mutex::new(Vec::<String>::new()) }) .await .lock() .await; dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(Mutex::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<Mutex<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.lock().await; lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD .get_or_init(|| async move { Mutex::new(Vec::<String>::new()) }) .await .lock() .await; let mut dirs = dirs.lock().await; lock.append(&mut dirs); } Ok(()) }
tokio::sync::RwLock
tokio::sync::OnceCellとRwLockの組み合わせです。こちらも非同期対応しています。同じくwrite lockのみ使用しています。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::Arc; use tokio::fs; // 1.32.0 use tokio::sync::{OnceCell, RwLock}; use tokio::task::JoinSet; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: tokio::sync::OnceCell<tokio::sync::RwLock<Vec<String>>> = OnceCell::const_new(); // #[tokio::main] #[tokio::main(flavor = "multi_thread", worker_threads = 5000)] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let _ = LIST_FROM_THD.set(RwLock::new(Vec::<String>::new())); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write() .await; lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write() .await; dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(RwLock::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<RwLock<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.write().await; lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write() .await; let mut dirs = dirs.write().await; lock.append(&mut dirs); } Ok(()) }
parking_lot::Mutex
tokio::sync::OnceCellとparking_lot::Mutexの組み合わせです。
parking_lot版はコンテキストスイッチが起きない場合に同じスレッドがロックを再取得しつづけないように考慮して公平性を保つ設計となっているようです。汚染されない事も利点の一つ用のようです。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::Arc; use tokio::fs; // 1.32.0 use tokio::sync::OnceCell; use tokio::task::JoinSet; use parking_lot::Mutex; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: tokio::sync::OnceCell<parking_lot::Mutex<Vec<String>>> = OnceCell::const_new(); #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let _ = LIST_FROM_THD.set(Mutex::new(Vec::<String>::new())); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD .get_or_init(|| async move { Mutex::new(Vec::<String>::new()) }) .await .lock(); lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD .get_or_init(|| async move { Mutex::new(Vec::<String>::new()) }) .await .lock(); dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(Mutex::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<Mutex<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.lock(); lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD .get_or_init(|| async move { Mutex::new(Vec::<String>::new()) }) .await .lock(); let mut dirs = dirs.lock(); lock.append(&mut dirs); } Ok(()) }
parking_lot::FairMutex
tokio::sync::OnceCellとparking_lot::FairMutexの組み合わせです。
parking_lot::Mutexとの違いはロック要求をキューに入れて公平性を保つ点のようです。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::Arc; use tokio::fs; // 1.32.0 use tokio::sync::OnceCell; use tokio::task::JoinSet; use parking_lot::FairMutex; // 0.12.1 #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: tokio::sync::OnceCell<parking_lot::FairMutex<Vec<String>>> = OnceCell::const_new(); #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let _ = LIST_FROM_THD.set(FairMutex::new(Vec::<String>::new())); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD .get_or_init(|| async move { FairMutex::new(Vec::<String>::new()) }) .await .lock(); lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD .get_or_init(|| async move { FairMutex::new(Vec::<String>::new()) }) .await .lock(); dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(FairMutex::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<FairMutex<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.lock(); lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD .get_or_init(|| async move { FairMutex::new(Vec::<String>::new()) }) .await .lock(); let mut dirs = dirs.lock(); lock.append(&mut dirs); } Ok(()) }
parking_lot::RwLock
tokio::sync::OnceCellとparking_lot::RwLockの組み合わせです。
設計的にはparking_lot::Mutexと同等のデザインのようです。FairRwLockは無いです。
use anyhow::Result; // 1.0.71 use clap::Parser; // 4.3.11 use std::sync::Arc; use tokio::fs; // 1.32.0 use tokio::sync::OnceCell; use tokio::task::JoinSet; use parking_lot::RwLock; // 0.12.1 #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// target directory #[arg(short, long, default_value_t = String::from(".") )] dir: String, } static LIST_FROM_THD: tokio::sync::OnceCell<parking_lot::RwLock<Vec<String>>> = OnceCell::const_new(); #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let dir_string = args.dir.to_string(); println!("{}", dir_string); let _ = LIST_FROM_THD.set(RwLock::new(Vec::<String>::new())); let mut dir_list; // Get lock and push dir { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write(); lock.push(dir_string); } // Like a do-while while { // Get lock and take array as snapshot { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write(); dir_list = lock.to_vec(); lock.clear(); } !dir_list.is_empty() } { let mut thds = JoinSet::new(); let mut dir_list = dir_list.iter(); while let Some(item) = dir_list.next() { thds.spawn(get_dirs(item.to_string())); } while let Some(thd) = thds.join_next().await { let _ = thd; } } Ok(()) } async fn get_dirs(dir: String) -> Result<()> { let mut entries = fs::read_dir(dir).await?; // Folder list let dirs = Arc::new(RwLock::new(Vec::new())); let mut thds = JoinSet::new(); while let Some(entry) = entries.next_entry().await? { let metadata = entry.metadata().await?; let path = entry.path(); thds.spawn( (|path: std::path::PathBuf, dirs: std::sync::Arc<RwLock<Vec<String>>>| async move { let path = path.display().to_string(); if metadata.is_dir() { println!("{}", path); // Get lock and add array { let mut lock = dirs.write(); lock.push(path); } } })(path.clone(), dirs.clone()), ); thds.spawn(async move { if let Ok(symlink) = fs::read_link(&path).await { if path.is_dir() { println!("{}@ -> {}", path.display(), symlink.display()); } } }); } while let Some(thd) = thds.join_next().await { let _ = thd; } // Get lock and add array { let mut lock = LIST_FROM_THD .get_or_init(|| async move { RwLock::new(Vec::<String>::new()) }) .await .write(); let mut dirs = dirs.write(); lock.append(&mut dirs); } Ok(()) }
計測結果
前回と同様にmultitimeで50回まわしてみます。比較としてデフォルト(#[tokio::main]のみ)設定の結果も付けました。
結果としては全体的に+5秒程度増えただけで大きなボトルネックになってはいないようです。あるいはロック解除待ち時間を非同期処理で効率的に行えているのかもしれません。
あまり差がないのでコンテキストスイッチ等他の要因が増分の可能性もあります。
何れにせよボトルネックにはなっていない、あるいはボトルネックになっていても非同期処理でカバーできているのだと思いますのでmutex系の処理速度に気を使う必要はなさそうです。
この中で使うとしたら汚染が無いなどメリットのあるparking_lotのものでしょうか。
しかしmutex系を使わなくても済みそうなので次回は無しバージョンのコードを書いてみたいと思います。
std::sync::Mutex
Mean | Std.Dev. | Min | Median | Max | |
real | 4.655 | 0.285 | 4.456 | 4.608 | 6.545 |
user | 3.866 | 0.178 | 3.321 | 3.861 | 4.522 |
sys | 17.694 | 0.847 | 17.042 | 17.593 | 23.193 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.311 | 0.297 | 5.633 | 6.256 | 7.909 |
user | 10.933 | 0.283 | 10.421 | 10.941 | 11.899 |
sys | 22.675 | 1.389 | 18.272 | 22.551 | 28.017 |
std::sync::RwLock
Mean | Std.Dev. | Min | Median | Max | |
real | 4.743 | 0.292 | 4.51 | 4.685 | 6.346 |
user | 4.393 | 0.228 | 4.005 | 4.367 | 5.265 |
sys | 17.74 | 0.89 | 16.809 | 17.546 | 22.366 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.629 | 0.258 | 6.172 | 6.603 | 7.759 |
user | 11.172 | 0.259 | 10.651 | 11.112 | 11.931 |
sys | 24.05 | 1.346 | 21.149 | 24.005 | 26.968 |
tokio::sync::Mutex
Mean | Std.Dev. | Min | Median | Max | |
real | 4.499 | 0.263 | 4.204 | 4.465 | 6.038 |
user | 3.765 | 0.194 | 3.443 | 3.722 | 4.242 |
sys | 17.186 | 0.835 | 16.148 | 16.978 | 21.732 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.419 | 0.355 | 5.856 | 6.38 | 7.94 |
user | 11.035 | 0.284 | 10.543 | 11.03 | 12.121 |
sys | 23.049 | 1.831 | 19.58 | 22.861 | 29.158 |
tokio::sync::RwLock
Mean | Std.Dev. | Min | Median | Max | |
real | 4.45 | 0.28 | 4.217 | 4.402 | 6.205 |
user | 3.737 | 0.171 | 3.347 | 3.7 | 4.119 |
sys | 17.014 | 0.893 | 16.099 | 16.891 | 22.417 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.474 | 0.315 | 5.715 | 6.437 | 7.94 |
user | 11.176 | 0.248 | 10.656 | 11.186 | 11.828 |
sys | 23.218 | 1.63 | 18.279 | 23.046 | 28.317 |
parking_lot::Mutex
Mean | Std.Dev. | Min | Median | Max | |
real | 4.505 | 0.277 | 4.311 | 4.447 | 6.31 |
user | 3.689 | 0.135 | 3.403 | 3.673 | 4.055 |
sys | 17.235 | 0.859 | 16.498 | 17.082 | 22.714 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.31 | 0.295 | 5.961 | 6.248 | 7.746 |
user | 10.817 | 0.278 | 10.204 | 10.767 | 11.705 |
sys | 22.759 | 1.516 | 20.328 | 22.48 | 27.164 |
parking_lot::FairMutex
Mean | Std.Dev. | Min | Median | Max | |
real | 4.497 | 0.265 | 4.334 | 4.453 | 6.278 |
user | 3.74 | 0.154 | 3.364 | 3.718 | 4.179 |
sys | 17.166 | 0.855 | 16.552 | 17.037 | 22.888 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.312 | 0.232 | 5.986 | 6.284 | 7.212 |
user | 10.889 | 0.266 | 10.451 | 10.839 | 11.949 |
sys | 22.702 | 1.235 | 20.304 | 22.633 | 25.248 |
parking_lot::RwLock
Mean | Std.Dev. | Min | Median | Max | |
real | 4.623 | 0.212 | 4.435 | 4.586 | 5.971 |
user | 3.894 | 0.153 | 3.347 | 3.925 | 4.179 |
sys | 17.569 | 0.664 | 16.862 | 17.471 | 21.602 |
Mean | Std.Dev. | Min | Median | Max | |
real | 6.259 | 0.304 | 5.551 | 6.26 | 7.784 |
user | 10.982 | 0.303 | 10.148 | 11.004 | 11.894 |
sys | 22.245 | 1.576 | 17.864 | 22.223 | 27.154 |