为什么我的简单 vec3 pyo3 pyclass 在构造和乘法方面比 py glm 的等效类慢得多?

问题描述 投票:0回答:1

我正在尝试在 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 更加一致?

python python-3.x rust ffi pyo3
1个回答
0
投票

众所周知,PyO3 的微性能与使用裸 CPython API 不同。 PyO3 自动添加的所有好处都有其开销,即使忽略这一点,也有大量可以在 PyO3 中完成但尚未完成的优化工作。请参阅 https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+performance

总的来说,我不推荐使用 PyO3 进行微优化,至少目前是这样。如果您有很多工作可以用本机代码完成,那就太好了;如果没有,这可能会付出很多努力而几乎毫无结果。

© www.soinside.com 2019 - 2024. All rights reserved.