開発

[rust] mutex系のパフォーマンスを調べてみる

前回の記事でasync/awaitの書き方を複数パターンを考えてみましたが今度はmutex系のパターンを試してみます。

ロックして排他的に読み書きできる機能は幾つかクレートが提供されていますがtokio標準ライブラリparking_lot提供のものを試してみます。

環境はvCPU=6、worker=5000です。

クレート概略
std::sync::Mutex標準ライブラリ
std::sync::RwLock標準ライブラリ。write lockのみ使用
tokio::sync::Mutextokio製
tokio::sync::RwLocktokio製。write lockのみ使用
parking_lot::Mutex通常よりパフォーマンスがよいらしいmutex
parking_lot::FairMutex通常よりパフォーマンスがよいらしいmutex。スレッドに偏りが少ないらしい
parking_lot::RwLockwrite 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

MeanStd.Dev.MinMedianMax
real4.6550.2854.4564.6086.545
user3.8660.1783.3213.8614.522
sys17.6940.84717.04217.59323.193
std::sync::Mutex, vCPU=6
MeanStd.Dev.MinMedianMax
real6.3110.2975.6336.2567.909
user10.9330.28310.42110.94111.899
sys22.6751.38918.27222.55128.017
std::sync::Mutex, vCPU=6, worker=5000

std::sync::RwLock

MeanStd.Dev.MinMedianMax
real4.7430.2924.514.6856.346
user4.3930.2284.0054.3675.265
sys17.740.8916.80917.54622.366
std::sync::RwLock, vCPU=6
MeanStd.Dev.MinMedianMax
real6.6290.2586.1726.6037.759
user11.1720.25910.65111.11211.931
sys24.051.34621.14924.00526.968
std::sync::RwLock, vCPU=6, worker=5000

tokio::sync::Mutex

MeanStd.Dev.MinMedianMax
real4.4990.2634.2044.4656.038
user3.7650.1943.4433.7224.242
sys17.1860.83516.14816.97821.732
tokio::sync::Mutex, vCPU=6,
MeanStd.Dev.MinMedianMax
real6.4190.3555.8566.387.94
user11.0350.28410.54311.0312.121
sys23.0491.83119.5822.86129.158
tokio::sync::Mutex, vCPU=6, worker=5000

tokio::sync::RwLock

MeanStd.Dev.MinMedianMax
real4.450.284.2174.4026.205
user3.7370.1713.3473.74.119
sys17.0140.89316.09916.89122.417
tokio::sync::RwLock, vCPU=6
MeanStd.Dev.MinMedianMax
real6.4740.3155.7156.4377.94
user11.1760.24810.65611.18611.828
sys23.2181.6318.27923.04628.317
tokio::sync::RwLock, vCPU=6, worker=5000

parking_lot::Mutex

MeanStd.Dev.MinMedianMax
real4.5050.2774.3114.4476.31
user3.6890.1353.4033.6734.055
sys17.2350.85916.49817.08222.714
parking_lot::Mutex, vCPU=6
MeanStd.Dev.MinMedianMax
real6.310.2955.9616.2487.746
user10.8170.27810.20410.76711.705
sys22.7591.51620.32822.4827.164
parking_lot::Mutex, vCPU=6, worker=5000

parking_lot::FairMutex

MeanStd.Dev.MinMedianMax
real4.4970.2654.3344.4536.278
user3.740.1543.3643.7184.179
sys17.1660.85516.55217.03722.888
parking_lot::FairMutex, vCPU=6
MeanStd.Dev.MinMedianMax
real6.3120.2325.9866.2847.212
user10.8890.26610.45110.83911.949
sys22.7021.23520.30422.63325.248
parking_lot::FairMutex, vCPU=6, worker=5000

parking_lot::RwLock

MeanStd.Dev.MinMedianMax
real4.6230.2124.4354.5865.971
user3.8940.1533.3473.9254.179
sys17.5690.66416.86217.47121.602
parking_lot::RwLock, vCPU=6
MeanStd.Dev.MinMedianMax
real6.2590.3045.5516.267.784
user10.9820.30310.14811.00411.894
sys22.2451.57617.86422.22327.154
parking_lot::RwLock, vCPU=6, worker=5000
Tags: rust
管理人

Recent Posts

CanvaがSerif (Affinity) を買収

私は使ったことがないのですが名前はよく聞…

4週間 ago

Serifのスプリングセール – アドオンが50%オフ

Affinity Photoなどレタッチ…

2か月 ago

音声がロボットのようになるときの対処

リモート会議などでたまに相手の音声がおか…

3か月 ago

Serifのブラックフライデー – 全品40%オフ V1ユーザは更にお得!

恒例のブラックフライデーセールが始まりま…

5か月 ago

[rust] rayonで書き直してみました

前回のコードを元にrayonを使った処理…

6か月 ago

[rust] async-stdで書き直してみました

前回のコードをasync-stdで書き直…

6か月 ago