Rust內存分配器的不同行爲

img

本文出自Rust內存分配器的不同行爲,同步於Rust中文社區專欄:Rust內存分配器的不同行爲 ,本文時間:2019-01-04, 作者:Pslydhh,簡介:Pslydhh

歡迎加入Rust中文社區,共建Rust語言中文網絡!歡迎向Rust中文社區專欄投稿,投稿地址 ,好文在以下地方直接展示, 歡迎訪問Rust中文論壇,QQ羣:570065685

  1. Rust中文社區首頁
  2. Rust中文社區文章專欄
  3. 知乎專欄Rust中文社區
  4. 思否專欄Rust中文社區
  5. 簡書專題Rust中文社區
  6. 微博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)是能夠回收內存的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章