本文出自Rust內存分配器的不同行爲,同步於Rust中文社區專欄:Rust內存分配器的不同行爲 ,本文時間:2019-01-04, 作者:Pslydhh,簡介:Pslydhh
歡迎加入Rust中文社區,共建Rust語言中文網絡!歡迎向Rust中文社區專欄投稿,投稿地址 ,好文在以下地方直接展示, 歡迎訪問Rust中文論壇,QQ羣:570065685
- Rust中文社區首頁
- Rust中文社區文章專欄
- 知乎專欄Rust中文社區
- 思否專欄Rust中文社區
- 簡書專題Rust中文社區
- 微博Rustlang-cn
對於如下的代碼,採用nightly version:
use std::sync::mpsc;
use std::thread;
fn main() {
const STEPS: usize = 1000000;
thread::sleep(std::time::Duration::from_millis(10));
let now = std::time::Instant::now();
let (tx1, rx1) = mpsc::channel();
for _ in 0..STEPS {
let _ = tx1.send(1);
// }
// for _ in 0..STEPS {
assert_eq!(rx1.try_recv().unwrap(), 1);
}
let elapsed = now.elapsed();
println!("recv duration: {} secs {} nanosecs\n", elapsed.as_secs(), elapsed.subsec_nanos());
thread::sleep(std::time::Duration::from_millis(10000));
}
在我的linux上,觀察輸出"recv duration..." 之後進程RES的佔用量:
- 如圖註釋:1824 kb
- 刪除註釋:48540 kb
直覺上來說這是令人奇怪的,因爲不管先send 1000000個元素,再try_recv這1000000個元素,或者 send/recv成對操作,操作完之後進程的內存佔用應該是幾乎一致的。
於是我提交了這個Issue:Different behaviors of allocator in nuanced snippets
對方表示在刪除註釋的情況下,一開始就send了百萬級別的對象,在進程中開闢了一塊非常大的內存,於是隨後的try_recv就不會回收內存了,這是一個幾乎所有內存分配器都會做的優化,因爲很可能你的程序隨後就會再次使用那一大塊內存。
這麼說也算是一種合理的選擇吧,在我們上面這樣單線程程序下沒什麼問題,但是在多線程的情況下呢?這種對於內存的重用能不能跨線程重用呢?畢竟假如一個線程保留了一大塊內存,另一個線程又保留一大塊內存,那麼進程本身會不會直接被killed呢?
我們來看與上述類似的一個例子:
use std::sync::mpsc;
use std::thread;
fn main() {
const STEPS: usize = 1000000;
thread::sleep(std::time::Duration::from_millis(10));
let now = std::time::Instant::now();
let t = thread::spawn(|| {
let t = thread::spawn(|| {
let (tx1, rx1) = mpsc::channel();
for _ in 0..STEPS {
let _ = tx1.send(1);
}
for _ in 0..STEPS {
assert_eq!(rx1.try_recv().unwrap(), 1);
}
});
t.join().unwrap();
let (tx1, rx1) = mpsc::channel();
for _ in 0..STEPS {
let _ = tx1.send(1);
}
for _ in 0..STEPS {
assert_eq!(rx1.try_recv().unwrap(), 1);
}
});
t.join().unwrap();
let (tx1, rx1) = mpsc::channel();
for _ in 0..STEPS {
let _ = tx1.send(1);
}
for _ in 0..STEPS {
assert_eq!(rx1.try_recv().unwrap(), 1);
}
let elapsed = now.elapsed();
println!("recv duration: {} secs {} nanosecs\n", elapsed.as_secs(), elapsed.subsec_nanos());
thread::sleep(std::time::Duration::from_millis(10000));
}
觀察輸出"recv duration..." 之後進程RES的佔用量:
- RES: 142364 kb
差不多是前面那個例子的三倍,注意本例子啓用了三個線程,也就是說,每個線程已經結束,它之前保留的內存居然還未歸還給OS,即使這些內存再也不能被使用到。
到這裏還只是內存不能使用。按照這個方式,有沒有可能在程序合理的情況下,進程直接被killed呢?
我們把上面的STEPS改爲50000000,在我的機器上直接被killed。如果把例子改一下,減少一個線程,那麼它又能合理的運行了。
對於這個問題,對方迴應稱 This is nothing Rust can control。也許通過修改內存分配器能夠修改它的行爲?
好在對於後面這個例子,目前的stable版本(rustc 1.31.1)是能夠回收內存的