Which is better for disk-bound, block_in_place vs spawn_blocking

for our web server we need direct access to Disk
and this component is one of hot component ....

I read tokio block_in_place is similar golang way
used for disk io operation

I read tokio spawn_blocking is similar erlang way
used for disk io operation

but which is better
block_in_place vs spawn_blocking ??

I tend to recommend spawn_blocking, but if it is a very hot part of your application, then you should benchmark to compare.

1 Like

I wrote a benchmark in both case
spawn_blocking was 2x times faster than block_in_place

iteration: 10_000
spawn_blocking => 54ms
block_in_place => ~100ms

But why is it faster ???

spawn_blocking code:

    let log = Arc::new(Mutex::new(LogFile::open("./tab-seg-1").unwrap()));
    // ------------------------------------------------



    let now = Instant::now();
    // ============================================

    for elem in 0..10_000 {
        
        let ilog = log.clone();
        tokio::task::spawn_blocking(move || {
            let mut log = ilog.lock().unwrap();
            if let Err(e) = log.write(&mut object_factory()) {
                panic!("{}", e);
            }
            let _ = log.flush();
        });

    }

    // ============================================
    let nnow = Instant::now();
    println!("==> {:?}", nnow.duration_since(now));

block_in_place code :

let mut log = LogFile::open("./tab-seg-1").unwrap();

    // ------------------------------------------------


    let now = Instant::now();
    // ============================================

    for elem in 0..10_000 {
        
        tokio::task::block_in_place(|| {
            if let Err(e) = log.write(&mut object_factory()) {
                panic!("{}", e);
            }
            let _ = log.flush();
        });

    }

    // ============================================
    let nnow = Instant::now();
    println!("==> {:?}", nnow.duration_since(now));

You aren't actually waiting for the spawned tasks to finish.

oh yes, with awaiting for all JoinHandle

Result is => 152.535268ms

block_in_place is better
but you recommend using spawn_blocking
have a specific reason ??

There are many reasons to prefer spawn_blocking. In the area of performance reasons, well, block_in_place can cause other tasks to get moved between threads a lot more, which can hurt the performance of those other tasks.

There are also some correctness challenges:

  1. You cannot really use block_in_place inside a task that uses within-task concurrency such as tokio::join!, tokio::select!, join_all, FuturesUnordered, StreamExt::buffer_unordered and so on.
  2. You cannot use block_in_place inside a current thread runtime or LocalSet.
3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.