以下の内容をお聞きしたいです。
背景
マルチスレッドについて少し触ったので配列を分割して和を、各スレッドで取得できるようにしようと考えました。
そこで以下のコードを作成しました。
- main.rs(playground)
use rand::prelude::*;
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn main() {
const N: usize = 10;
const SIZE: usize = 10000000;
let mut tasks = vec![];
println!("[make array]");
let arr = create_rand_array(SIZE);
let result = Arc::new(AtomicUsize::new(0));
for i in 0..N - 1 {
let picked_arr = pick_array(i * N, SIZE / N, &arr);
let result = Arc::clone(&result);
let handle = thread::spawn(move || {
result.fetch_add(sum(picked_arr), Ordering::SeqCst);
});
tasks.push(handle);
}
for task in tasks {
task.join().unwrap();
}
println!("sum: {:?}", result);
}
fn sum(arr: &[usize]) -> usize {
let mut result: usize = 0;
arr.iter().for_each(|num| {
result += num
});
result
}
fn create_rand_array(num: usize) -> Vec<usize> {
let mut arr: Vec<usize> = Vec::with_capacity(num);
let mut rng = rand::thread_rng();
(1..num).for_each(|_| {
arr.push(rng.gen::<usize>() % 1000);
});
arr
}
fn pick_array(start: usize, num: usize, arr: &[usize]) -> &[usize] {
if start >= 0 && start + num < arr.len() {
&arr[start..start + num - 1]
} else {
panic!("Invalid Args: start -> {}, num -> {}, arr.len() -> {}", start, num, arr.len());
}
}
このコードを実行したところ
Compiling playground v0.0.1 (/playground)
error[E0597]: `arr` does not live long enough
--> src/main.rs:15:54
|
15 | let picked_arr = pick_array(i * N, SIZE / N, &arr);
| ----------------------------^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `arr` is borrowed for `'static`
...
28 | }
| - `arr` dropped here while still borrowed
となりました。
自分としては
for task in tasks {
task.join().unwrap();
}
があるからarr
の参照であるpicked_arr
がdropされるまで(thread::spawn
のクロージャを抜けるまで)、arr
は存在できるのではないかと考えています。
お聞きしたいこと
- このコンパイルエラーを解消する方法をお聞きしたいです
Box::leak
やchunk
を知らなかったので、勉強になりました。1点お聞きしたいのですが、提示いただいたプログラムだとforeachでjoinしているから、mapでspawn→join→mapでspawn→join...
という結局シリアルな処理になってしまうのかと思ったのですが、どうでしょうか?実行時間を測ったところ、分割しないでfoldした方が100倍程度早くなっていました。 – tbt Dec 15 '21 at 14:05SIZE/N
にして、foreachではtasksにpushしその後join()を呼び出すとやったら、早くなりました。お騒がせしましたmm – tbt Dec 15 '21 at 14:24SIZE=100000000, N=10
までは上記の対処した方が早かったのですが、SIZE=1000000000, N=10
になったらarr.chunk.for_each
内でjoinした方が早くなりました。arr.chunk.for_each
内でjoinした場合(Playground)と、arr.chunk.for_each
内ではtasksにpushして後からtasksのfor文内でjoinした場合(Playground)の挙動の違いを教えていただけたらと思います。 – tbt Dec 15 '21 at 14:46SIZE/N
を使うとプログラムの意味が変わってしまうので、それで速くなるのは関係ない気がします。 – sei0o Dec 17 '21 at 01:19tasks
というあらかじめ生成されたVec
にpush()
する場合には、tasks
用のメモリ領域が必要です。一方、collect()
を使って一旦Vec
にまとめる場合は内部でFromIterator::from_iter()
が呼び出されます(イテレータからVec
にする関数)。Vec
のfrom_iter()
の実装はイテレータが持っていた領域をなるべく使い回すようになっていて、メモリを節約できます。Vecの内部実装には詳しくないので解釈には自信がありませんが… – sei0o Dec 17 '21 at 01:41SIZE/N
個のスレッドを生成します。条件によってはスレッド数が多すぎて、逆に遅くなります(比較用のPlaygroundを回答に追記しました)。 – sei0o Dec 17 '21 at 02:12