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



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

我最近一直在使用rust编写python插件,这给python的cpu密集型任务提供了显著的加速,与c相比,它也更快。然而它有一个缺点是,与使用cython加速的旧方案相比,rust(我使用pyo3)的调用开销似乎大于c(我使用cython),

例如,我们在这里得到一个空的python函数:
def empty_function():
return 0

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

如果我们把它编译成一个cython插件,使用相同的源代码:

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

执行时间将减少到40纳秒。这意味着我们可以使用python进行一些细粒度的嵌入,并且我们可以期望它总是比本地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

这里还值得注意的是,用python setup.py build_ext --inplace编译rust扩展将它们构建为非优化模式(python setup.py developpip install -e .也是如此)。

以下是命令

的输出
Finished dev [unoptimized + debuginfo] target(s) in 0.02s

内置"release"模式,使用:

pip install .

使用pip install . --verbose,您可以看到差异:

Finished release [optimized] target(s) in 1.02s

这可以使大量差异,在我的情况下,未优化的构建比优化的构建慢9倍。

最新更新