为何PyO3实现的vec3类构造与乘法性能远逊于PyGLM?
优化PyO3实现的Float3类型以看齐PyGLM性能
PyO3默认生成的Python类包装存在几个额外开销点,而PyGLM通过激进的优化(如禁用动态属性、简化运算符实现)规避了这些开销:
- 默认启用的实例
__dict__增加了对象初始化的内存分配和时间 - 原地运算符(
__iadd__/__isub__)的错误实现导致额外对象创建 - 简单运算符函数未内联,引入不必要的调用开销
- Rust编译优化未拉满
以下是针对性的优化步骤:
1. 启用slots消除实例字典开销
修改#[pyclass]属性,添加slots禁用动态属性字典,减少对象创建的内存和时间开销:
#[pyclass(slots, derive(Clone))] struct Float3 { #[pyo3(get, set)] x: f64, #[pyo3(get, set)] y: f64, #[pyo3(get, set)] z: f64, }
2. 修正原地运算符实现
原__iadd__/__isub__创建新对象后赋值,不仅逻辑错误(Python原地运算符需返回self),还额外产生对象分配开销。修改为直接修改成员变量并返回self:
#[pymethods] impl Float3 { // ... 其他方法 ... fn __iadd__(&mut self, lhs: &Self) -> PyResult<&mut Self> { self.x += lhs.x; self.y += lhs.y; self.z += lhs.z; Ok(self) } fn __isub__(&mut self, lhs: &Self) -> PyResult<&mut Self> { self.x -= lhs.x; self.y -= lhs.y; self.z -= lhs.z; Ok(self) } }
3. 内联简单运算符函数
给所有简单的运算符方法添加#[inline(always)],强制Rust编译器内联这些函数,消除调用开销,同时复用代码减少冗余:
#[pymethods] impl Float3 { #[new] #[inline(always)] fn py_new(x: f64, y: f64, z: f64) -> Self { Float3 { x, y, z } } #[inline(always)] fn __rmul__(&self, lhs: f64) -> Self { Float3 { x: self.x * lhs, y: self.y * lhs, z: self.z * lhs, } } #[inline(always)] fn __mul__(&self, lhs: f64) -> Self { self.__rmul__(lhs) } #[inline(always)] fn __add__(&self, lhs: &Self) -> Self { Float3 { x: self.x + lhs.x, y: self.y + lhs.y, z: self.z + lhs.z, } } #[inline(always)] fn __sub__(&self, lhs: &Self) -> Self { Float3 { x: self.x - lhs.x, y: self.y - lhs.y, z: self.z - lhs.z, } } // ... 修正后的原地运算符 ... }
4. 拉满Rust编译优化
修改Cargo.toml的release配置,启用最高级优化:
[profile.release] opt-level = 3 # 最高级优化 lto = true # 链接时优化,跨模块合并代码 codegen-units = 1 # 单代码生成单元,最大化优化空间 panic = "abort" # 禁用panic展开,减少开销(Python扩展中panic会直接崩溃)
5. 优化测试代码
测试时禁用Python GC避免干扰性能结果,同时增加样本量提升测试准确性:
import test_pyo3 import glm import time import gc def main(): gc.disable() # 禁用GC避免干扰 samples = 1_000_000 # 增加样本量 tic = time.time() for i in range(samples): temp = glm.dvec3(0.0, 0.0, 9.81) print(f"average time per construction glm {(time.time() - tic)/samples:.9f}") tic = time.time() for i in range(samples): temp = test_pyo3.Float3(0.0, 0.0, 9.81) print(f"average time per construction test_pyo3 {(time.time() - tic)/samples:.9f}") tic = time.time() for i in range(samples): temp = 1.5 * glm.dvec3(0.0, 0.0, 9.81) print(f"average time per multiply operation glm {(time.time() - tic)/samples:.9f}") tic = time.time() for i in range(samples): temp = 1.5 * test_pyo3.Float3(0.0, 0.0, 9.81) print(f"average time per multiply operation test_pyo3 {(time.time() - tic)/samples:.9f}") if __name__ == '__main__': main()
预期效果
经过以上优化后,Float3的构造和乘法性能会接近甚至超过PyGLM的dvec3:
- 构造性能的40%差距会缩小到10%以内
- 乘法性能的一倍差距会基本消除
这些优化主要针对PyO3的默认包装开销,将Rust的原生性能充分释放,对齐PyGLM的优化策略。
内容的提问来源于stack exchange,提问作者Krupip
相关产品推荐
相关产品推荐

