分享
  1. 首页
  2. 文章

Rust内存分配器的不同行为

krircc · · 1237 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

本文出自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)是能够回收内存的


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:krircc

查看原文:Rust内存分配器的不同行为

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1237 次点击
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏