为什么 cython 嵌入式插件在 cpython 解释器中比 rust-c 接口版本具有更高的性能?

问题描述

想问一些关于python解释器底层原理的问题,因为我自己搜索的时候没有得到太多有用的信息。

我最近一直在使用 rust 编写 python 插件,这大大加快了 python 的 cpu 密集型任务,并且与 c 相比,它的编写速度也更快。但是它有一个缺点就是,相比旧的使用cython加速的方案,rust(我用的是pyo3)的调用开销似乎比c(我用的是cython)要大,

例如,我们在这里得到一个空的python函数

def empty_function():
    return 0

在 Python 中通过 for 循环调用它一百万次并计算时间,以便我们可以发现每次调用大约需要 70 纳秒(在我的电脑中)。

如果我们使用相同的源代码将其编译为 cython 插件

# test.pyx
cpdef unsigned int empty_function():
    return 0

执行时间将减少到 40 纳秒。这意味着我们可以使用 cython 进行一些细粒度的嵌入,并且我们可以期望它总是比原生 python 执行得更快。

但是说到 Rust,(老实说,我现在更喜欢使用 rust 进行插件开发而不是 cython,因为不需要在语法上做一些奇怪的黑客攻击),调用时间将增加到 140 纳秒,几乎是两倍与原生 python 一样多。源代码如下:

use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

#[pyfunction]
fn empty_function() -> usize {
    0
}

#[pymodule]
fn testlib(_py: Python,m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(empty_function,m)?)?;
    Ok(())
}

这意味着rust不适合python的细粒度嵌入式替换。如果有一个任务的调用时间很少,每次调用都需要很长时间,那么使用 rust 是完美的。但是如果有一个任务会在代码中被大量调用,那么它似乎不适合使用 rust,因为类型转换的开销会占用大部分加速时间。

我想知道这是否可以解决,更重要的是,我想知道这种差异的根本原因。 cpython解释器在它们之间调用时是否有某种区别,比如调用c插件cpython和pypy之间的区别?我可以从哪里获得更多信息?谢谢。

===

更新:

抱歉各位,没想到我的问题会模棱两可,毕竟三者的源码都已经给出了,使用timeit来测试函数运行时几乎是python开发中的约定俗成。

我的测试代码与@Jmb 注释中的代码几乎完全相同,但有一些细微差别,我使用 python setup.py build_ext --inplace 方式构建而不是裸 gcc,但这应该没有任何区别。不管怎样,谢谢补充。

解决方法

正如评论中所建议的,这是一个自我回答。

由于评论区的讨论没有得出明确的结论,所以我去 pyo3 的 repo 中提出了一个问题,并从其主要维护者那里得到了回应。

总之,结论就是pyo3或者cython编译的插件在cpython调用的时候没有本质区别。当前速度差异来自优化深度不同。

这是问题的链接: https://github.com/PyO3/pyo3/issues/1470