Rust中函数返回Vec<f32>为何出现大幅性能损耗?
Rust分位数计算的性能差异问题
问题场景
内联逻辑的情况
将分位数计算逻辑内联在main函数中时,release模式下总耗时仅44.484µs,代码如下:
fn main() -> Result<(), ()> { // ... Some code for loading data, ends up with: let series: ChunkedArray<Float32Type> let start = Instant::now(); let percentiles = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; let mut result = Vec::<f32>::with_capacity(percentiles.len()); for p in percentiles { match series.quantile(p as f64, QuantileInterpolOptions::Lower) { Ok(v) => result.push(v.unwrap()), _ => {} } } let duration = start.elapsed(); println!("Time elapsed is: {:?}", duration); println!("{}",result); Ok(()) }
提取为独立函数的情况
将逻辑提取为独立的percentile函数后,main中调用函数的总耗时骤升至约72ms,但函数内部逻辑耗时仅13µs左右;调试模式下总耗时更是达到约600ms,代码如下:
fn main() -> Result<(), ()> { // ... Some code for loading data, ends up with: let series: ChunkedArray<Float32Type> let start = Instant::now(); let percentiles = percentile(series); let duration = start.elapsed(); println!("Time elapsed is: {:?}", duration); println!("{}",result); Ok(()) } pub fn percentile(data: Series) -> Vec<f32> { let data = data; let start = Instant::now(); let percentiles = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; let mut result = Vec::<f32>::with_capacity(percentiles.len()); for p in percentiles { match data.quantile(p as f64, QuantileInterpolOptions::Lower) { Ok(v) => result.push(v.unwrap()), _ => {} } } let duration = start.elapsed(); println!("internal time: {:?}", duration); result }
经测试,原代码中series的销毁耗时仅241µs,无法解释该差异。疑问:为何函数返回Vec<f32>会导致如此巨大的性能损耗?忽略了什么?
核心原因分析
不是返回Vec<f32>导致的性能问题,真正的元凶是**ChunkedArray<Float32Type>转Series的深克隆开销**:
- 内联版本中,直接使用
series(ChunkedArray类型)调用quantile,没有任何类型转换开销; - 提取函数时,你将
ChunkedArray<Float32Type>传给了接受Series参数的percentile函数,这里触发了ChunkedArray到Series的转换——而polars库中的Series克隆属于深克隆,会复制底层所有数据,这部分开销发生在函数调用之前(参数传递阶段),不在你函数内部的计时范围内,所以会出现"函数内部耗时极短但总耗时暴增"的现象。
调试模式下性能极差是因为Rust默认关闭了所有优化,同时插入了大量调试信息,类型转换的开销被进一步放大,属于正常现象。
解决方案
修改percentile函数的参数类型,直接接受ChunkedArray<Float32Type>的引用,避免不必要的深克隆:
pub fn percentile(data: &ChunkedArray<Float32Type>) -> Vec<f32> { let start = Instant::now(); let percentiles = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; let mut result = Vec::<f32>::with_capacity(percentiles.len()); for p in percentiles { match data.quantile(p as f64, QuantileInterpolOptions::Lower) { Ok(v) => result.push(v.unwrap()), _ => {} } } let duration = start.elapsed(); println!("internal time: {:?}", duration); result }
在main中调用时传递引用:
let percentiles = percentile(&series);
这样既保留了代码的模块化,又完全消除了类型转换的开销,性能会和内联版本一致。
内容的提问来源于stack exchange,提问作者Crispy Holiday
相关产品推荐
相关产品推荐

