研究带有 Rust 扩展的 Ruby gem 的性能

Investigating the performance of a Ruby gem with Rust extension

提问人:Filip Pacanowski 提问时间:11/16/2023 最后编辑:Filip Pacanowski 更新时间:11/16/2023 访问量:78

问:

介绍

在阅读了 Ruby 优于 C 的文章后,我开始好奇 Rust 的性能如何。因此,我开始研究一个带有 Rust 扩展的 Ruby gem,它实现了 GraphQL 解析器。对于解析器实现,我使用的是 graphql_parser crate。为了与 Ruby 运行时进行交互,我按照本指南使用 magnus

我的方法

我编写了一些 Rust 代码,这些代码遍历了 AST 生成的 AST 并将其转换为 Ruby 对象(哈希和数组)。代码如下所示:graphql_parser

fn parse(query: String) -> Result<RHash, Error> {
    match parse_query::<String>(&query) {
        Ok(r) => return Ok(translation::translate_document(&r)),
        Err(e) => return Err(Error::new(exception::runtime_error(), e.to_string())),
    }
}

fn translate_document(doc: &Document<'_, String>) -> RHash {
    let hash = build_ruby_node("document");
    let definitions = RArray::new();
    for x in doc.definitions.iter() {
        definitions.push(translate_definition(x)).unwrap();
    }
    hash.aset(Symbol::new("definitions"), definitions).unwrap();
    return hash;
}

fn translate_definition(definition: &Definition<'_, String>) -> RHash {
    return match definition {
        Definition::Operation(operation) => translate_operation_definition(operation),
        Definition::Fragment(fragment) => translate_fragment_definition(fragment),
    };
}

// Many more functions that follow the structure produced by graphql_parser...

fn build_ruby_node(node_type: &str) -> RHash {
    let hash = RHash::new();
    hash.aset(Symbol::new("node_type"), Symbol::new(node_type))
        .unwrap();
    return hash;
}

从本质上讲,我有一堆递归函数,它们与 定义的文档结构非常相似。完整代码位于 GitHub 存储库中graphql_parser

基准

我希望所描述的方法具有类似于以下内容的性能:

fn parse_raw(query: String) -> String {
    let ast = parse_query::<&str>(&query);
    return format!("#{:?}",ast);
}

我的想法是,这基本上是做同样的事情:遍历 AST 并生成一个具有相同数据的 Ruby 对象。但是,基准测试显示我的代码实际上慢了 ~5 倍:

$ bundle exec ruby benchmark.rb
Warming up --------------------------------------
               parse     4.000  i/100ms
           parse_raw    24.000  i/100ms
Calculating -------------------------------------
               parse     53.968  (± 3.7%) i/s -    272.000  in   5.050661s
           parse_raw    245.115  (± 2.4%) i/s -      1.248k in   5.094948s

我用 Ruby 3.2.2 () 运行了这个。ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]

问题

我是 Rust 初学者。我想了解代码中的性能瓶颈在哪里。我做错了什么吗?分配 Ruby 对象本身就很慢吗?有没有办法让这个宝石的性能更高?

Ruby Rust 图形QL-Ruby

评论

1赞 Chayim Friedman 11/16/2023
首先,将做更少的分配。其次,我假设创建 Ruby 对象需要调用 CRuby API,这需要一些簿记,而不仅仅是分配对象。format!()
0赞 Chayim Friedman 11/16/2023
我对 Ruby 还不够了解,但在 Python 中,如果我遇到这种情况,解决方案是在 Rust 和 Python 之间传递尽可能少的数据。例如,与其将整个解析的树分配并返回给 Python,不如将执行代码移植到 Rust 中,并且只有 .execute(data_to_send_to_graphql)
1赞 Max 11/18/2023
这将是更多的工作,但由于 Ruby 是鸭子类型的,因此返回一个对象可能会更有效率,该对象只是包装你的 rust 数据并具有类似哈希的接口。我不知道 magnus 是怎么做到的,但在 C API 中我会使用并为我的对象定义 alloc/init 方法。TypedData_Wrap_StructTypedData_Get_Struct

答: 暂无答案