dpc / mioco.pre-0.9

Scalable, coroutine-based, asynchronous IO handling library for Rust programming language. (aka MIO COroutines).
Mozilla Public License 2.0
457 stars 30 forks source link

Optimising repeated select!s #127

Open lhallam opened 8 years ago

lhallam commented 8 years ago

When using select in a loop the event's don't have to be re-registered every time (which, for channels at least, is expensive).

To test, I made mioco::thread::tl_current_coroutine public, and added a flag to mioco::coroutine::Coroutine to disable the deregister_all call in unblock. Here's a stress test using channels:

loop.rs - The test using mioco master:

#[macro_use] extern crate mioco;

fn main(){
    let _ = mioco::start(move ||{

        let (sx,rx) = mioco::sync::mpsc::channel();
        let (sx2,rx2) = mioco::sync::mpsc::channel();
        let (sx3,rx3) = mioco::sync::mpsc::channel();
        let mut counter = 0;
        sx.send(42);
        sx2.send(42);
        sx3.send(42);

        loop{select!(
            r:rx2 => { let _ = rx2.try_recv(); },
            r:rx3 => { let _ = rx3.try_recv(); },
            r:rx => {
                if let Ok(x) = rx.try_recv(){
                    counter += 1;
                    sx.send(42);
                    if counter > 10_000_000 { break; }
                }
            },
            );
        }
    });
}

loop2.rs - With tl_current_coroutine public and auto_deregister added to coroutine.

#[macro_use] extern crate mioco;
use mioco::thread::tl_current_coroutine;
use mioco::Evented;

fn main(){
    let _ = mioco::start(move ||{

        let (sx,rx) = mioco::sync::mpsc::channel();
        let (sx2,rx2) = mioco::sync::mpsc::channel();
        let (sx3,rx3) = mioco::sync::mpsc::channel();
        let mut counter = 0;
        sx.send(42);
        sx2.send(42);
        sx3.send(42);

        unsafe{
            tl_current_coroutine().auto_deregister = false;
            rx.select_add(mioco::RW::read());
            rx2.select_add(mioco::RW::read());
            rx3.select_add(mioco::RW::read());
        }

        loop{
            let ret = mioco::select_wait();
            if ret.id() == rx.id(){
                if let Ok(_) = rx.try_recv(){
                    counter += 1;
                    sx.send(42);
                    if counter > 10_000_000 { break; }
                }
            }
            if ret.id() == rx2.id(){ let _ = rx2.try_recv(); }
            if ret.id() == rx3.id(){ let _ = rx3.try_recv(); }
        }
    });
}

and the results:

➜  mioco_timer git:(master) ✗  time ./target/release/loop
./target/release/loop  13.27s user 0.51s system 98% cpu 13.929 total

➜  mioco_timer git:(master) ✗  time ./target/release/loop2
./target/release/loop2  10.11s user 0.34s system 98% cpu 10.592 total

One idea for a proper implementation of this would be a select_loop! macro, which would select in a loop without re-registering.

dpc commented 8 years ago

Originally mioco was (re-/de-)registering stuff only when needed internally. But back then any IO that you wanted to use would have to be "wrapped" first to create a mioco type that could not be moved between threads (!Send !Sync) etc so the IO registered in one coroutine couldn't suddenly dissapear from the thread. I had to change this in:

https://github.com/dpc/mioco/commit/4cd58ef28c94f13bb1ca6bc9c8c4633f330cbcd0

as wrapping was very inconvenient for users. To support this, any IO would get "registered" in EventLoop only on blocking the coroutine, and always deregistered when resuming it.

I guess what you proposing could in fact help somewhat, and I like the idea.