我正在尝试在 Pyo3 中创建一个 Python 扩展,它创建一个类似于 vec3/glm vec3 的类型,但来自 rust。
我创建了以下目录结构:
.
├── Cargo.toml
├── pyproject.toml
├── python
│ └── main.py
└── src
└── lib.rs
其中只有
lib.rs
和main.py
是定制的,其余的都是由maturin init
生成的
lib.rs
是:
use pyo3::prelude::*;
#[pyclass]
#[derive(Clone)]
struct Float3{
#[pyo3(get, set)]
x : f64,
#[pyo3(get, set)]
y : f64,
#[pyo3(get, set)]
z : f64,
}
#[pymethods]
impl Float3 {
#[new]
fn py_new(x : f64, y : f64, z : f64) -> Self {
Float3 { x, y, z}
}
fn __rmul__ (&self, lhs : f64) -> Self{
return Float3{ x: self.x * lhs, y : self.y * lhs, z : self.z * lhs};
}
fn __mul__ (&self, lhs : f64) -> Self{
return Float3{ x: self.x * lhs, y : self.y * lhs, z : self.z * lhs};
}
fn __add__ (&self, lhs : &Self) -> Self{
return Float3{x : self.x + lhs.x, y: self.y + lhs.y, z: self.z + lhs.z};
}
fn __sub__ (&self, lhs : &Self) -> Self{
return Float3{x : self.x - lhs.x, y: self.y - lhs.y, z: self.z - lhs.z};
}
fn __iadd__ (&mut self, lhs : &Self) -> (){
*self = Float3{x : self.x + lhs.x, y: self.y + lhs.y, z: self.z + lhs.z};
}
fn __isub__ (&mut self, lhs : &Self) -> (){
*self = Float3{x : self.x - lhs.x, y: self.y - lhs.y, z: self.z - lhs.z};
}
}
/// A Python module implemented in Rust.
#[pymodule]
fn test_pyo3(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Float3>()?;
Ok(())
}
并且
main.py
是:
import test_pyo3
import glm
import time
def main():
samples = 100000
tic = time.time()
for i in range(samples):
temp = glm.dvec3(0.0, 0.0, 9.81)
print("average time per construction glm {}".format((time.time() - tic) / samples))
tic = time.time()
for i in range(samples):
temp = test_pyo3.Float3(0.0, 0.0, 9.81)
print("average time per construction test_pyo3 {}".format((time.time() - tic) / samples))
tic = time.time()
for i in range(samples):
temp = 1.5 * glm.dvec3(0.0, 0.0, 9.81)
print("average time per multiply operation glm {}".format((time.time() - tic) / samples))
tic = time.time()
for i in range(samples):
temp = 1.5 * test_pyo3.Float3(0.0, 0.0, 9.81)
print("average time per multiply operation test_pyo3 {}".format((time.time() - tic) / samples))
pass
if __name__ == '__main__':
main()
main.py 运行构建 glm.dvec3 的基准,与标量相乘,以及我的类的等效项。
输出是:
average time per construction glm 2.125263214111328e-07
average time per construction test_pyo3 2.834796905517578e-07
average time per construction glm 2.486872673034668e-07
average time per construction test_pyo3 4.966330528259277e-07
我使用应在 release 中构建的“maturindevelop-r”编译 Rust 部分,并在我的 Cargo.toml 中将 pyo3 设置为 pyo3 =“0.21.2”
简单标量乘法的构造之间存在 40 左右的百分比,并且性能降低了近 2 倍。当使用 py-spy 查看调用图时,我看到的只是到处都是“蹦床”和一堆随机数,并且不清楚到底发生了什么。无论如何,这应该是同类比较,PyGLM 只是直接使用 C 绑定,而不是使用 Rust 来这样做。我不认为如此简单的测试用例在性能上会有巨大差异,两者都应该做相同类型的工作。
有没有办法让我的简单类的性能与 PyGLM 更加一致?
众所周知,PyO3 的微性能与使用裸 CPython API 不同。 PyO3 自动添加的所有好处都有其开销,即使忽略这一点,也有大量可以在 PyO3 中完成但尚未完成的优化工作。请参阅 https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+performance。
总的来说,我不推荐使用 PyO3 进行微优化,至少目前是这样。如果您有很多工作可以用本机代码完成,那就太好了;如果没有,这可能会付出很多努力而几乎毫无结果。