2个版本

0.1.1 2019年11月25日
0.1.0 2019年11月25日

#485 in 内存管理

Apache-2.0

10KB
188

RealBox:让Box再次伟大!

背景:隐藏的内存拷贝

众所周知,Box<T>首先在栈上分配内存,并将初始化的结构体复制到堆上。因此,在嵌入式设备上创建大型boxed对象会导致栈溢出而不是堆分配器OOM。

copyless希望通过直接调用分配原语来解决它,并导致使用ptr::write,其定义如下

#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub unsafe fn write<T>(dst: *mut T, src: T) {
    intrinsics::move_val_init(&mut *dst, src)
}

intrinsics是一个“内联符号”,编译器后端可以识别它。注释中提到

if let Some(sym::move_val_init) = intrinsic {
    // `move_val_init` has "magic" semantics - the second argument is
    // always evaluated "directly" into the first one.

然而,这并不总是正确的。在调试构建中,rustc仍然会触发memcpy

448a:       48 8b 7c 24 38          mov    0x38(%rsp),%rdi
448f:       48 89 ce                mov    %rcx,%rsi
4492:       ba 94 01 00 00          mov    $0x194,%edx
4497:       48 89 44 24 30          mov    %rax,0x30(%rsp)
449c:       e8 e7 f9 ff ff          callq  3e88 <memcpy@plt>

我的结论是:move_val_init的保证取决于优化,这可能无法由Rust保证。

解决方案

关键区别在于这个crate提供的API需要

impl<T> RealBox<T, Global> {
    pub fn heap_init<F>(initialize: F) -> Box<T>
    where
        F: Fn(&mut T),
    {
        unsafe {
            let mut t = Self::new_in(Global).into_box();
            initialize(t.as_mut());
            t
        }
    }
}

一个初始化器 Fn(&mut T),并且不依赖于move_val_init

用法

#[derive(Debug)]
struct Obj {
    x: u32,
    y: f64,
    a: [u8; 4],
}

let stack_obj = Obj {
    x: 12,
    y: 0.9,
    a: [0xff, 0xfe, 0xfd, 0xfc],
};

let heap_obj = RealBox::<Obj>::heap_init(|mut t| {
    t.x = 12;
    t.y = 0.9;
    t.a = [0xff, 0xfe, 0xfd, 0xfc]
});

无运行时依赖