1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Command パターンの実装についての考察

Posted at

この記事のcommandパターンの実装を見て少し気になったので動的ディスパッチでどれくらい遅くなるかベンチマークを取って比較してみた。

なお例の通りでもよかったが、値を使用している感じ(データベースなら入力する値)と状態を持っている感じ(データベースの状態)を表したかったので要件を変えている。
具体的には数字を入れて決められた数を足すか、掛けるか、ゼロにするというコマンドを実装してみた。

実装法

トレイトオブジェクト

挙げられてた手法1
Fnのトレイトオブジェクトも同じなのでここに含めている

pub trait Command {
    fn ops(&self, value: i32) -> i32;
}

pub struct AddOp(i32);
impl Command for AddOp {
    fn ops(&self, value: i32) -> i32 {
        self.0 + value
    }
}
pub struct MulOp(i32);
impl Command for MulOp {
    fn ops(&self, value: i32) -> i32 {
        self.0 * value
    }
}
pub struct ToZero;
impl Command for ToZero {
    fn ops(&self, _value: i32) -> i32 {
        0
    }
}

関数ポインタ

挙げられてた手法2
トレイトオブジェクトより早いといわれていたが…。
状態が欲しかったので状態を表すフィールドとともに渡している。

pub struct FuncCommand(pub fn(i32, i32) -> i32, pub i32);
impl Command for FuncCommand {
    fn ops(&self, value: i32) -> i32 {
        (self.0)(self.1, value)
    }
}

enum

操作が決まっているならenumを使うべきではないか?

 pub enum CommandEnum {
    Add(i32),
    Mul(i32),
    ToZero,
}
impl Command for CommandEnum {
    fn ops(&self, value: i32) -> i32 {
        match self {
            CommandEnum::Add(i) => value + i,
            CommandEnum::Mul(i) => value * i,
            CommandEnum::ToZero => 0,
        }
    }
}

enum + トレイトオブジェクト

決まっている操作に拡張性を加える手法。この時ToZeroを拡張する操作とした。

pub enum CommandDynEnum {
    Add(i32),
    Mul(i32),
    Dynamic(Box<dyn Command>),
}
impl Command for CommandDynEnum {
    fn ops(&self, value: i32) -> i32 {
        match self {
            CommandDynEnum::Add(i) => value + i,
            CommandDynEnum::Mul(i) => value * i,
            CommandDynEnum::Dynamic(x) => x.ops(value),
        }
    }
}

ベンチマーク

ランダムにコマンドを発生させた。どのベンチでも同じコマンドが生成するようにしている。また生成はベンチに含んでいない。

Generate Commands

pub fn list_enum(seed: u64, length: usize) -> Vec<CommandEnum> {
    let mut vec = Vec::with_capacity(length);
    let mut rand = StdRng::seed_from_u64(seed);
    let three = Uniform::from(0..=2);
    let twelve = Uniform::from(0..=12);
    for _ in 0..length {
        match three.sample(&mut rand) {
            0 => vec.push(CommandEnum::Add(twelve.sample(&mut rand))),
            1 => vec.push(CommandEnum::Mul(twelve.sample(&mut rand))),
            2 => vec.push(CommandEnum::ToZero),
            _ => unreachable!(),
        }
    }
    vec
}

pub fn list_enum_box(seed: u64, length: usize) -> Vec<CommandDynEnum> {
    let mut vec = Vec::with_capacity(length);
    let mut rand = StdRng::seed_from_u64(seed);
    let three = Uniform::from(0..=2);
    let twelve = Uniform::from(0..=12);
    for _ in 0..length {
        match three.sample(&mut rand) {
            0 => vec.push(CommandDynEnum::Add(twelve.sample(&mut rand))),
            1 => vec.push(CommandDynEnum::Mul(twelve.sample(&mut rand))),
            2 => vec.push(CommandDynEnum::Dynamic(Box::new(ToZero))),
            _ => unreachable!(),
        }
    }
    vec
}

pub fn list_box_dyn(seed: u64, length: usize) -> Vec<Box<dyn Command>> {
    let mut vec: Vec<Box<dyn Command>> = Vec::with_capacity(length);
    let mut rand = StdRng::seed_from_u64(seed);
    let three = Uniform::from(0..=2);
    let twelve = Uniform::from(0..=12);
    for _ in 0..length {
        match three.sample(&mut rand) {
            0 => vec.push(Box::new(AddOp(twelve.sample(&mut rand)))),
            1 => vec.push(Box::new(MulOp(twelve.sample(&mut rand)))),
            2 => vec.push(Box::new(ToZero)),
            _ => unreachable!(),
        }
    }
    vec
}

pub fn list_fn(seed: u64, length: usize) -> Vec<FuncCommand> {
    let mut vec: Vec<FuncCommand> = Vec::with_capacity(length);
    let mut rand = StdRng::seed_from_u64(seed);
    let three = Uniform::from(0..=2);
    let twelve = Uniform::from(0..=12);
    for _ in 0..length {
        match three.sample(&mut rand) {
            0 => vec.push(FuncCommand(|x, y| x + y, twelve.sample(&mut rand))),
            1 => vec.push(FuncCommand(|x, y| x * y, twelve.sample(&mut rand))),
            2 => vec.push(FuncCommand(|_, _| 0, 0)),
            _ => unreachable!(),
        }
    }
    vec
}
Criterion
fn command_patterns(c: &mut Criterion) {
    let mut g = c.benchmark_group("command_pattern");
    let seed = 1234_u64;
    g.bench_with_input("box_dyn", &seed, |b, seed| {
        let vec = list_box_dyn(*seed, 1000);
        b.iter(|| {
            let mut s = 0;
            for x in &vec {
                s = x.ops(s);
            }
            black_box(s)
        })
    });
    g.bench_with_input("enum_box", &seed, |b, seed| {
        let vec = list_enum_box(*seed, 1000);
        b.iter(|| {
            let mut s = 0;
            for x in &vec {
                s = x.ops(s);
            }
            black_box(s)
        })
    });
    g.bench_with_input("enum", &seed, |b, seed| {
        let vec = list_enum(*seed, 1000);
        b.iter(|| {
            let mut s = 0;
            for x in &vec {
                s = x.ops(s);
            }
            black_box(s)
        })
    });
    g.bench_with_input("fn", &seed, |b, seed| {
        let vec = list_fn(*seed, 1000);
        b.iter(|| {
            let mut s = 0;
            for x in &vec {
                s = x.ops(s);
            }
            black_box(s)
        })
    });
}

criterion_group!(benches, command_patterns);
criterion_main!(benches);
トレイトオブジェクト 4.6625 us 4.7189 us 4.7899 us
関数ポインタ 3.9561 us 3.9816 us 4.0092 us
enum 493.79 ns 497.79 ns 502.07 ns
enum + トレイトオブジェクト 1.3763 us 1.4585 us 1.5440 us

というわけでenumが最速で桁が一つ違った。意外だったのは関数ポインタが遅かったことか。ここまで差がないならわざわざ関数ポインタを使う必要性を感じないがどうだろうか。

ところでなぜ『Rust Design Patterns』のコマンドパターンでenumの紹介がなかったのだろうか。それともこのパターンの使い方を誤解しているのだろうか。

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?