Rust の非同期ランタイムの実行速度を比較してみる

## 環境
stable-x86_64-unknown-linux-gnu
rustc 1.41.0 (5e1a79984 2020-01-27)


## 動機
[This Week in Rust](https://this-week-in-rust.org/) で[こんな記事](https://stjepang.github.io/2020/01/25/build-your-own-block-on.html)を見つけたので Rust の非同期ランタイムの実行速度が気になって比較してみました。
[tokio](https://crates.io/crates/tokio)、[actix-rt](https://crates.io/crates/actix-rt)（内部的には tokio）、[futures](https://crates.io/crates/futures)、[async-std](https://crates.io/crates/async-std) の4つを比較します。

## 計測
```toml:Cargo.toml
[dependencies]
tokio = "0.2"
actix-rt = "1.0"
futures = "0.3"
async-std = "1.4"
```

```rust:main.rs
use tokio;
use actix_rt;
use futures;
use async_std;

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Instant;

fn main() {
    let mut rt = tokio::runtime::Runtime::new().unwrap();
    let now = Instant::now();
    rt.block_on(Yields(100));
    println!("tokio: {:?}", now.elapsed());

    let mut rt = actix_rt::Runtime::new().unwrap();
    let now = Instant::now();
    rt.block_on(Yields(100));
    println!("actix_rt: {:?}", now.elapsed());

    let now = Instant::now();
    futures::executor::block_on(Yields(100));
    println!("futures: {:?}", now.elapsed());

    let now = Instant::now();
    async_std::task::block_on(Yields(100));
    println!("async_std: {:?}", now.elapsed());
}

struct Yields(i32);

impl Future for Yields {
    type Output = ();
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        if self.0 == 0 {
            Poll::Ready(())
        } else {
            self.0 -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}
```

## 結果
| crate            |          time|
|:-----------------|-------------:|
| tokio            |     126.455µs|
| actix-rt         |     138.074µs|
| futures          |       7.504µs|
| async-std        |      84.609µs|
futures が圧倒的に早い。

## おまけ
[async-std のスケジューラが新しくなる](https://async.rs/blog/stop-worrying-about-blocking-the-new-async-std-runtime/)ようなので、試してみます。

```toml:Cargo.toml
[dependencies]
async-std = { git = 'https://github.com/stjepang/async-std', branch = 'new-scheduler' }
```

| crate                    |          time|
|:-------------------------|-------------:|
| async-std (new-scheduler)|       7.541µs|
futures と同じぐらいですね。以上。

## 追記
tokio とその他 crates のパフォーマンスにあまりに差があるので調べました。
~~tokio のランライムは Thread Pool を使用するので、そのオーバーヘッドが大きいようです~~ （[下記](#%E3%82%82%E3%81%86%E3%81%A1%E3%82%87%E3%81%A3%E3%81%A8%E3%81%A0%E3%81%91%E7%B6%9A%E3%81%8F)参照）。Single Thread の executor を使用するには下記のようになります。（詳細は [Runtime Configurations](https://docs.rs/tokio/0.2.11/tokio/runtime/index.html#runtime-configurations) を参照）

```rust
let mut basic_rt = tokio::runtime::Builder::new()
        .basic_scheduler().build().unwrap();
let now = Instant::now();
basic_rt.block_on(Yields(100));
println!("tokio (single thread): {:?}", now.elapsed());
```
| crate                   |         time|
|:------------------------|------------:|
| tokio (single thread)   |       10.5µs|
用途に合わせて適切にランタイムを使い分ける必要がありますね。

## もうちょっとだけ続く
ちゃんと [Feature Flag](https://docs.rs/tokio/0.2.11/tokio/#feature-flags) を立てないと Thread Pool は有効にならないのでした…。

```toml:Cargo.toml
[dependencies]
tokio = { version = "0.2", features = ["rt-threaded"] }
futures = { version = "0.3", features = ["thread-pool"] }
```
```rust
let mut rt = tokio::runtime::Runtime::new().unwrap();
let now = Instant::now();
rt.block_on(Yields(100));
println!("tokio (thread pool): {:?}", now.elapsed());

let mut basic_rt = tokio::runtime::Builder::new()
    .basic_scheduler().build().unwrap();
let now = Instant::now();
basic_rt.block_on(Yields(100));
println!("tokio (single thread): {:?}", now.elapsed());

use futures::task::SpawnExt;
let pool = futures::executor::ThreadPool::new().unwrap();
let handle = pool.spawn_with_handle(Yields(100)).unwrap();
let now = Instant::now();
futures::executor::block_on(handle);
println!("futures (thread pool): {:?}", now.elapsed());

let now = Instant::now();
futures::executor::block_on(Yields(100));
println!("futures (single thread): {:?}", now.elapsed());
```
| crate                  |        time|
|:-----------------------|-----------:|
| tokio (thread pool)    |     6.633µs|
| tokio (single thread)  |    10.551µs|
| futures (thread pool)  |    46.969µs|
| futures (single thread)|     5.824µs|
う、う〜ん:thinking:.。oO（この比較意味あるの？）

思いつきで書き始めた記事だけど、ちょっとだけ Rust 非同期ランタイムに詳しくなれたような気がした。