我正在考虑使用Rayon的并行迭代器功能,但我担心迭代小集合的性能。
并行性开销有时会导致小型集合的减速。如果我为多线程做必要的准备,那么迭代超过2个元素比使用单线程版本要慢。如果我有4000万个元素,并行性将给我一个线性的性能提升。
我读到了关于ParallelIterator::weight
(0.6.0)的内容,但是我不明白我是否应该为小型收藏品优化这种转角情况,或者如果Rayon是聪明的并处理引擎盖下的所有内容。
if collection_is_small() {
// Run single threaded version...
} else {
// Use parallel iterator.
}
已处理元素的ParallelIterator::weight
为1.有关良好定义的信息,请参阅相关文档,但处理单个元素的成本很低。
谷歌发给我一个旧的文档页面。从版本0.8.0开始,Weight
被弃用并且removed。
你可以凭经验看到这种行为不能得到保证:
use rayon::prelude::*; // 1.0.3
use std::thread;
fn main() {
let ids: Vec<_> = (0..2)
.into_par_iter()
.map(|_| thread::current().id())
.collect();
println!("{:?}", ids);
}
该程序的各种运行显示:
[ThreadId(1), ThreadId(2)]
[ThreadId(1), ThreadId(1)]
[ThreadId(2), ThreadId(1)]
[ThreadId(2), ThreadId(2)]
话虽这么说,你应该执行自己的基准测试。默认情况下,Rayon创建一个全局线程池并使用工作窃取来平衡线程之间的工作。线程池是每个进程的一次性设置成本,并且工作窃取有助于确保工作仅在需要时跨越线程边界。这就是为什么上面的输出都使用相同的线程。
权重API已被弃用,有利于split length control。默认情况下,Rayon将在每个项目上进行拆分,有效地使所有计算并行,这种行为可以通过with_min_len进行配置。
设置在每个线程中处理所需的最小迭代器长度。人造丝不会分裂任何小于这个长度,但当然迭代器可能已经开始变小了。
拉链和交错等生产商将使用两个最小值中的较大者。 flat_map中的链式迭代器和迭代器可以各自使用它们自己的最小长度。
extern crate rayon; // 1.0.3
use rayon::prelude::*;
use std::thread;
fn main() {
println!("Main thread: {:?}", thread::current().id());
let ids: Vec<_> = (0..4)
.into_par_iter()
.with_min_len(4)
.map(|_| thread::current().id())
.collect();
println!("Iterations: {:?}", ids);
}
输出:
Main thread: ThreadId(0)
Iterations: [ThreadId(0), ThreadId(0), ThreadId(0), ThreadId(0)]
Playground(感谢@shepmaster代码)