rust 動的ディスパッチと静的ディスパッチの調査
##環境
rustc 1.27.1
##概要
動的ディスパッチと静的ディスパッチってどっちが速いの?って思ったから検証してみました。
検証自体は、コンパイラの事詳しくないので正直ガバガバだと思いますが、rustのいい練習になったので記事にします。
動的ディスパッチ
動的ディスパッチの特徴を下記に記します。
- コードが膨張しないが低速な仮想関数の呼び出しが必要
- 正確な型は実行時になって初めて判明する
- インライン化(コンパイラによる最適化手法)ができない
詳しく知りたい人は、同じくrustのチュートリアルページにある、トレイトオブジェクトのページを見てください。
トレイトオブジェクト
静的ディスパッチ
静的ディスパッチの特徴を下記に記します。
- 呼び出される関数はコンパイル時に分かっている
- インライン化可能
- 同じ関数をそれぞれの型毎に幾つもコピーするためバイナリが膨張する
詳しく知りたい人は、同じくrustのチュートリアルページにある、トレイトオブジェクトのページを見てください。
トレイトオブジェクト
プログラムを組む1
クロージャを作成し、それを動的ディスパッチと静的ディスパッチの2通りの方法で呼び出し、時間を計測してみます。
端末で入力するバージョン(rust)
fn main(){
// クロージャの作成
let add_one = |x: i32| x + 1;
loop{
println!("試行回数を入力してください");
let mut trial_count = String::new();
io::stdin().read_line(&mut trial_count)
.expect("端末からの読み取りに失敗");
let trial_count: u64 = match trial_count.trim().parse(){
Ok(num) => num,
Err(_) => continue,
};
println!("{}回ループさせます", trial_count);
// 静的ディスパッチ
fn static_fanc<F>(some_closure: F) -> i32
where F : Fn(i32) -> i32 {
some_closure(1)
}
let start = Instant::now();
for _ in 0..trial_count{
static_fanc(add_one);
}
println!("静的なディスパッチ:{:?}", start.elapsed());
// 動的ディスパッチ
fn dynamic_fanc(some_closure: &Fn(i32) -> i32) -> i32{
some_closure(1)
}
let start = Instant::now();
for _ in 0..trial_count {
dynamic_fanc(&add_one);
}
println!("動的ディスパッチ:{:?}", start.elapsed());
}
}
以下は、10000と入力した例
出力
試行回数を入力してください
10000
10000回ループさせます
静的なディスパッチ:Duration { secs: 0, nanos: 1666373 }
動的ディスパッチ:Duration { secs: 0, nanos: 1439735 }
プログラムを組む2
わかりにくいので、csvにしてグラフにすることにしました。
試行回数は、とりあえず5万回。
csvを出力するバージョン(rust)
use std::time::{Instant};
use std::io::prelude::*;
use std::fs::File;
fn main(){
let s = format!("rust.csv");
let mut buffer = File::create(s).unwrap();
// クロージャの作成
let add_one = |x: i32| x + 1;
for trial_count in 1..50000{
// 静的ディスパッチ
fn static_fanc<F>(some_closure: F) -> i32
where F : Fn(i32) -> i32 {
some_closure(1)
}
let start = Instant::now();
for _ in 0..trial_count {
static_fanc(add_one);
}
let static_time = start.elapsed();
// 動的ディスパッチ
fn dynamic_fanc(some_closure: &Fn(i32) -> i32) -> i32{
some_closure(1)
}
let start = Instant::now();
for _ in 0..trial_count {
dynamic_fanc(&add_one);
}
let dynamic_time = start.elapsed();
if trial_count % 1000 == 0 {
write!(buffer, "{}, {}, {}\n",
trial_count,
static_time.subsec_nanos() / 1_000,
dynamic_time.subsec_nanos() / 1_000).unwrap();
}
}
}
次に、pythonを使います。
グラフ描画(python)
import csv
import numpy as np
import matplotlib.pyplot as plt
try:
with open("./rust.csv", 'rt') as fin:
reader = csv.reader(fin)
trial_count = [i[0] for i in reader]; fin.seek(0)
static_time = [i[1] for i in reader]; fin.seek(0)
dynamic_time = [i[2] for i in reader]
except:
print("エラー発生。プログラム終了。")
sys.exit()
# 描画範囲の指定
print(trial_count)
print(static_time)
x = list(map(int, trial_count))
y1 =list(map(int, static_time))
y2 = list(map(int, dynamic_time))
# グラフ描画設定
plt.plot(x, y1, color="b", label="static")
plt.plot(x, y2, color="r", label="dynamic")
plt.ylabel("time(micro)")
plt.xlabel("trial_count")
plt.title('static & dynamic')
# ラベルの描画
plt.legend()
# グラフの描画実行
plt.show()
グラフの表示
上記プログラムで作成したグラフを以下に掲載します。
1回目
2回目
3回目
3つのグラフに規則性が見られません。
なんか、凄い跳ね上がってる所が多々あるぐらいです。
単純なプログラムでは、あまり変わらないということでしょうか。