Rust内存分配器的不同行为

栏目: 编程语言 · Rust · 发布时间: 5年前

内容简介:本文出自对于如下的代码,采用nightly version:

本文出自 Rust内存分配器的不同行为 ,同步于 Rust中文社区专栏:Rust内存分配器的不同行为 ,本文时间:2019-01-04, 作者: Pslydhh ,简介:Pslydhh

欢迎加入 Rust中文社区,共建Rust语言中文网络!欢迎向Rust中文社区专栏投稿, 投稿地址 ,好文在以下地方直接展示, 欢迎访问 Rust中文论坛 ,QQ群:570065685

  1. Rust中文社区首页
  2. Rust中文社区 文章专栏

对于如下的代码,采用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)是能够回收内存的


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法设计与分析

算法设计与分析

屈婉玲、刘田、张立昂、王捍贫 / 清华大学 / 2011-5 / 25.00元

《算法设计与分析》为计算机科学技术专业核心课程“算法设计与分析”教材.全书以算法设计技术和分析方法为主线来组织各知识单元,主要内容包括基础知识、分治策略、动态规划、贪心法、回溯与分支限界、算法分析与问题的计算复杂度、NP完全性、近似算法、随机算法、处理难解问题的策略等。书中突出对问题本身的分析和求解方法的阐述,从问题建模、算法设计与分析、改进措施等方面给出适当的建议,同时也简要介绍了计算复杂性理论......一起来看看 《算法设计与分析》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具