この記事はRustその3 Advent Calendar 2019 12/14の記事です。
並列処理ライブラリrayonの紹介です。
rayonとは?
rayonは、スレッドをいい感じに使って、非常に気軽に並列処理ができるライブラリです。
配列の各要素に対して並列処理: par_iter
最も手軽に利用できるのが、use rayon::prelude::*;
だけですぐに利用できるpar_iter
です。
配列の各要素に対して並列処理を行うことができます。iter()
をpar_iter()
に置き換えるだけで、配列操作の感覚で処理の並列化ができます!
par_iter()した後に使えるメソッドはここから見られます。
extern crate rayon;
use rayon::prelude::*;
let food_prices = [100, 120, 2000, 1840, 600, 100, 120, 2000];
let takeout_price = food_prices
.par_iter()
.map(|&i| (i as f32 * 1.08) as i32) // これ以降の処理が並列処理に!
.reduce_with(|m, n| m + n)
.unwrap();
println!("{}", takeout_price);
カスタムな並列処理
上記のpar_iter
以外にも、自分でカスタマイズした並列処理を行うためのAPIが用意されています。
2つの並列処理: join
2つの処理の結果を利用したい場合、join
を使います。分割統治法を使うアルゴリズムのような、1つの大規模な処理をたくさんの小さな処理に分割できるケースで適用できます。
use rayon::join;
fn hell_fibonacci(x: u64) -> u64 {
if x < 2 {
return x;
}
let (prev, prevprev) = join(|| hell_fibonacci(x - 1), || hell_fibonacci(x - 2));
prev + prevprev
}
println!("{}", hell_fibonacci(5));
任意の数の並列処理: scope
joinでは扱えない、任意の数の並列処理はscopeで行います。自由にタスクをspawnできます。
use rayon::scope;
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
scope(|s| {
s.spawn(move |_| {
COUNTER.fetch_add(1, Ordering::SeqCst);
});
s.spawn(move |_| {
COUNTER.fetch_add(2, Ordering::SeqCst);
});
s.spawn(move |_| {
COUNTER.fetch_add(3, Ordering::SeqCst);
});
});
println!("COUNT:{:?}", COUNTER);
ベンチマーク
リポジトリ内のベンチマーク に入っている階乗計算のベンチマークを、手元の環境(MacBook Pro Retina 13inch 4コア/8スレッド)で実行してみました。
test factorial::factorial_iterator bench: 15,679,758 ns/iter (+/- 817,472)
test factorial::factorial_par_iter bench: 2,262,074 ns/iter (+/- 153,986)
test factorial::factorial_recursion bench: 4,060,433 ns/iter (+/- 394,378)
test factorial::factorial_join bench: 2,129,283 ns/iter (+/- 190,471)
_par_iterが_iteratorの並列化に、_joinが_recursionの並列化に対応しています。
このベンチマークでは9999!を計算してますが、十分並列化による効果が出ているという印象です。