rust - Rust 中的动态调度替代方案

标签 rust dynamic-dispatch

我正在解析命令字符串并创建由不同类型的节点组成的抽象语法树(AST)。我正在尝试找出一种有效的方法来执行该命令。

此应用对性能极其敏感。它适用于在用户等待时需要处理数百万个项目的查询引擎。

在非 Rust 语言中执行此操作的标准方法是让每个节点类型实现一个公共(public)接口(interface)。然而,要在 Rust 中使用一个特征来做到这一点,你必须使用笨拙的 Box 语法,并且这需要在每次调用时进行动态调度。例如,

trait Node {
    fn get(&mut self) -> u32;
}

struct MyParentNode {
    my_int: u32,
    left: Box<dyn Node>,
    right: Box<dyn Node>,
}

impl Node for MyParentNode {
    fn get(&mut self) -> u32 {
        self.left.get() + self.right.get()
    }
}

struct MyLeafNode {
    my_int: u32,
}

impl Node for MyLeafNode {
    fn get(&mut self) -> u32 {
        self.my_int
    }
}

我看到的基准表明,与调用具体函数相比,动态调度非常慢。

另一种选择是使用枚举作为节点类型,但也好不了多少:

enum NodeEnum {
    Parent(Box<ParentInfo>),
    Leaf(Box<LeafInfo>),
}

impl NodeEnum {
    fn get(&mut self) -> u32 {
        match self {
            NodeEnum::Parent(parent) => parent.get(),
            NodeEnum::Leaf(leaf) => leaf.get(),
        }
    }
}

struct ParentInfo {
    left: NodeEnum,
    right: NodeEnum,
}

impl ParentInfo {
    fn get(&mut self) -> u32 {
        self.left.get() + self.right.get()
    }
}

struct LeafInfo {
    my_int: u32,
}

impl LeafInfo {
    fn get(&mut self) -> u32 {
        self.my_int
    }
}

(上面的代码可能看起来并不糟糕,但在真实的应用程序中,每个节点上会有几十个节点类型和十几个方法,因此有很多对 match 的调用)。

必须有更好的方法。有没有什么方法可以实现这一点,而无需动态调度的开销,或者每次调用 get() 时都必须调用 match

函数指针有帮助吗?

最佳答案

请在下面找到一个围绕您的问题的快速而肮脏的示例。

我知道我的计时方式(但是在 Release模式下,并在 cargo flamegraph 的帮助下)不够严格(为此目的存在一些工具);这给人一种粗粒度的感觉。

测试了两种解决方案。 第一个使用一些动态调度。 第二个使用 enum_dispatch crate为了用一些(自动)match 语句替换虚拟表。 性能有了显着提升(在本例中和在我的计算机上分别为 4.7 秒和 2.0 秒)。

请注意,如果实际用例中的节点执行的操作远多于本示例中的简单添加,则调度成本可能并不显着。 最后,只有对您的实际用例进行计时才能告诉我们所有这些是否有益。

注意:在我的第一个答案中,我考虑了在计时中创建节点,因为我不知道用例。显然,大部分时间都花在分配/释放上,我测试了第三个版本以缓解这种情况。另一方面,如果我们认为节点一次性构建并多次使用(如这个新答案),那么调度成本就会变得很大。

use enum_dispatch::enum_dispatch;

fn main() {
    let node_count = 200;
    let iter_count = 1_000_000;
    let warmup = iter_count / 100;
    with_dyn(node_count, iter_count, warmup);
    with_enum(node_count, iter_count, warmup);
}
/*
~~~~ with_dyn() ~~~~
4.712342453s
~~~~ with_enum() ~~~~
1.950779271s
*/

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

trait DynNodeTrait {
    fn get(&self) -> i32;
    fn change(
        &mut self,
        offset: i32,
    );
}

struct ParentDynNode {
    left: Box<dyn DynNodeTrait>,
    right: Box<dyn DynNodeTrait>,
}
impl DynNodeTrait for ParentDynNode {
    fn get(&self) -> i32 {
        self.left.get() + self.right.get()
    }

    fn change(
        &mut self,
        offset: i32,
    ) {
        self.left.change(offset);
        self.right.change(offset);
    }
}

struct LeafDynNode {
    my_int: i32,
}
impl DynNodeTrait for LeafDynNode {
    fn get(&self) -> i32 {
        self.my_int
    }

    fn change(
        &mut self,
        offset: i32,
    ) {
        self.my_int += offset;
    }
}

fn with_dyn(
    node_count: i32,
    iter_count: usize,
    warmup: usize,
) {
    println!("~~~~ with_dyn() ~~~~");
    let r1 = node_count * (node_count + 1) / 2;
    let r2 = r1 + 100 * node_count;
    let mut root: Box<dyn DynNodeTrait> = Box::new(LeafDynNode { my_int: 1 });
    for i in 2..=node_count {
        let leaf = Box::new(LeafDynNode { my_int: i });
        root = Box::new(ParentDynNode {
            left: root,
            right: leaf,
        });
    }
    let mut t0 = std::time::Instant::now();
    for iter in 0..iter_count {
        if iter < warmup {
            t0 = std::time::Instant::now();
        }
        let r = root.get();
        if r != r1 {
            println!("{} != {}", r, r1);
        }
        root.change(100);
        let r = root.get();
        if r != r2 {
            println!("{} != {}", r, r2);
        }
        root.change(-100);
    }
    let t1 = std::time::Instant::now();
    println!("{:?}", t1 - t0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#[enum_dispatch]
trait EnumNodeTrait {
    fn get(&self) -> i32;
    fn change(
        &mut self,
        offset: i32,
    );
}

#[enum_dispatch(EnumNodeTrait)]
enum EnumNode {
    Parent(ParentEnumNode),
    Leaf(LeafEnumNode),
}

struct ParentEnumNode {
    left: Box<EnumNode>,
    right: Box<EnumNode>,
}
impl EnumNodeTrait for ParentEnumNode {
    fn get(&self) -> i32 {
        self.left.get() + self.right.get()
    }

    fn change(
        &mut self,
        offset: i32,
    ) {
        self.left.change(offset);
        self.right.change(offset);
    }
}

struct LeafEnumNode {
    my_int: i32,
}
impl EnumNodeTrait for LeafEnumNode {
    fn get(&self) -> i32 {
        self.my_int
    }

    fn change(
        &mut self,
        offset: i32,
    ) {
        self.my_int += offset;
    }
}

fn with_enum(
    node_count: i32,
    iter_count: usize,
    warmup: usize,
) {
    println!("~~~~ with_enum() ~~~~");
    let r1 = node_count * (node_count + 1) / 2;
    let r2 = r1 + 100 * node_count;
    let mut root = Box::new(EnumNode::from(LeafEnumNode { my_int: 1 }));
    for i in 2..=node_count {
        let leaf = Box::new(EnumNode::from(LeafEnumNode { my_int: i }));
        root = Box::new(EnumNode::from(ParentEnumNode {
            left: root,
            right: leaf,
        }));
    }
    let mut t0 = std::time::Instant::now();
    for iter in 0..iter_count {
        if iter < warmup {
            t0 = std::time::Instant::now();
        }
        let r = root.get();
        if r != r1 {
            println!("{} != {}", r, r1);
        }
        root.change(100);
        let r = root.get();
        if r != r2 {
            println!("{} != {}", r, r2);
        }
        root.change(-100);
    }
    let t1 = std::time::Instant::now();
    println!("{:?}", t1 - t0);
}

关于rust - Rust 中的动态调度替代方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75827353/

相关文章:

sqlite - `rusqlite::types::to_sql::ToSql` 未实现特征 `serde_json::value::Value`

java - 为什么方法重载决策是在编译时确定的?

python - 引用纯虚方法

c++ - 非虚基的多态成员类

unit-testing - 您如何测试特定的 Rust 错误?

json - 如何将所有字段都是默认值的类型反序列化为 None ?

ruby - 嵌套单例类方法查找

rust - 创建可变迭代器时出现生命周期错误

rust - 如何在不移出参数的情况下实现 std::ops:{Add, Sub, Mul, Div} 运算符之一?