7个稳定版本
1.2.1 | 2024年5月8日 |
---|---|
1.2.0 | 2022年6月15日 |
1.1.2 | 2022年4月28日 |
1.1.1 | 2021年6月27日 |
1.0.1 | 2021年3月27日 |
#98 in 内存管理
66 每月下载量
用于 2 crates
37KB
559 行
安全运行时栈分配
提供方法,以便Rust可以安全地访问和使用运行时栈分配的缓冲区(如C VLAs或alloca()
函数)。这是通过一个辅助函数完成的,该函数接受一个FnOnce
闭包,该闭包以栈分配的缓冲区切片为参数。切片仅在闭包返回时被认为是有效的,此时栈将恢复到辅助函数的调用者。如果您需要一个可移动的缓冲区,请使用Vec
或静态大小的数组。内存是在闭包调用者的栈帧上分配的,当调用者返回时进行释放。
此切片将根据安全Rust对切片的期望进行适当的构造。但是,仍然可能通过分配过多内存而导致栈溢出,因此请谨慎使用,并且永远不要盲目地分配未检查的栈内存。
要求
该crate适用于稳定或夜间Rust,但需要符合C99规范的编译器来构建。
功能
no_std
- 在夜间工具链上启用no_std
支持。
示例
在栈上分配字节数组。
fn copy_with_buffer<R: Read, W: Write>(mut from: R, mut to: W, bufsize: usize) -> io::Result<usize>
{
alloca_zeroed(bufsize, move |buf| -> io::Result<usize> {
let mut read;
let mut completed = 0;
while { read = from.read(&mut buf[..])?; read != 0} {
to.write_all(&buf[..read])?;
completed += read;
}
Ok(completed)
})
}
任意类型
在栈上分配任何类型的切片。
stackalloc(5, "str", |slice: &mut [&str]| {
assert_eq!(&slice[..], &["str"; 5]);
});
释放
包装器处理需要释放的类型。
stackalloc_with(5, || vec![String::from("string"); 10], |slice| {
assert_eq!(&slice[0][0][..], "string");
}); // The slice's elements will be dropped here
MaybeUninit
您可以直接获取对齐的栈内存,无需初始化。
stackalloc_uninit(5, |slice| {
for s in slice.iter_mut()
{
*s = MaybeUninit::new(String::new());
}
// SAFETY: We have just initialised all elements of the slice.
let slice = unsafe { stackalloc::helpers::slice_assume_init_mut(slice) };
assert_eq!(&slice[..], &vec![String::new(); 5][..]);
// SAFETY: We have to manually drop the slice in place to ensure its elements are dropped, as `stackalloc_uninit` does not attempt to drop the potentially uninitialised elements.
unsafe {
std::ptr::drop_in_place(slice as *mut [String]);
}
});
它是如何工作的?
由于Rust没有在运行时操作栈的方法,我们使用FFI调用一个函数来操作其帧以在该处分配所需的内存。然后,此函数调用回调,传递此栈分配内存的指针。一旦回调返回,FFI函数将处理重置栈指针到其调用者的栈指针。
与直接在调用者的帧上分配内存相比,这在许多方面都有益处。
- 在所有支持
alloca
的语言中,它都被实现为编译器内建功能。这是因为编译器必须在函数返回的任何位置插入重置栈指针的指令,它还必须跟踪在alloca
调用之后推入栈中的任何内容的地址。手动执行此操作将阻止编译器管理栈指针的能力,并防止它不仅确定当前栈帧的大小,而且确定其布局。因此,这可能是更有效率的。 - 在调用者的帧上直接分配内存意味着内存将保留到函数 返回,而不是直到它超出作用域。这可能是导致错误的微妙来源,例如,在循环中调用
alloca()
导致栈溢出的风险。因此,这也更安全,且风险更低。 - 以这种方式实现
alloc
需要特定平台的内联汇编。这不仅是不稳定的 Rust 功能,而且非常不安全,因为编译器可能会基于栈布局的假设,这些假设随后会被无效化。目前还没有方法来实现这种行为的安全的alloca
,因为这样的实现需要用户非常了解在哪里以及如何使用它。
缺点
这要以额外的函数调用为代价,这个函数调用可能无法内联(即使有 LTO 和其他激进优化)。与堆分配的缓冲区相比,栈分配的缓冲区的权衡在一般情况下可能不值得,但这个额外的间接引用可能会在微观尺度上影响性能。如果您对性能至关重要,那么您应该无论如何都要对这些事物进行基准测试。这可能是在您的用例中堆分配的 Vec
更高效,或者栈分配的切片可能更高效。
性能
对于小型(1k 或更小)元素数组,stackalloc
可以比 Vec
高出约 50% 或更多。这种性能差异随着分配的内存量增长而减小。
test tests::bench::stackalloc_of_uninit_bytes_known ... bench: 3 ns/iter (+/- 0)
test tests::bench::stackalloc_of_uninit_bytes_unknown ... bench: 3 ns/iter (+/- 0)
test tests::bench::stackalloc_of_zeroed_bytes_known ... bench: 22 ns/iter (+/- 0)
test tests::bench::stackalloc_of_zeroed_bytes_unknown ... bench: 17 ns/iter (+/- 0)
test tests::bench::vec_of_uninit_bytes_known ... bench: 13 ns/iter (+/- 0)
test tests::bench::vec_of_uninit_bytes_unknown ... bench: 55 ns/iter (+/- 0)
test tests::bench::vec_of_zeroed_bytes_known ... bench: 36 ns/iter (+/- 2)
test tests::bench::vec_of_zeroed_bytes_unknown ... bench: 37 ns/iter (+/- 0)
许可
MIT 许可证
无运行时依赖
~205KB