我有我的异步函数:
async fn work<const BUFFER_SIZE: usize>() {
let mut buffer = [MaybeUninit::<u8>::uninit(); BUFFER_SIZE]; // Big chunk stack memory
yield_now().await; // Force flow control back, save the buffer state
buffer[0] = MaybeUninit::new(0x42); // use the buffer later when resuming from the await point
}
脱糖函数返回的
impl Future
表示包含 buffer
的 FSM执行,因为状态需要跨等待点保存。
所以我最终得到了一个
Future
对象,其内存布局大小约为 BUFFER_SIZE
字节。这没什么大不了的:buffer
初始化(以及Future
构造)是一个简单而快速的堆栈分配(只是一个sub rsp, BUFFER_SIZE
汇编指令,甚至没有初始化)。
但是,当需要在内存中移动对象时,拥有大堆栈内存占用对象就变得相关(至少在我的情况下)。整个内存布局是
memmove
位于新位置(因此,对于 buffer
的每次移动都会复制整个 Future
)。
看起来(使用
tokio
和 glib-rs
进行测试)这些运行时确实在执行/轮询期间不断移动 Future
内存。
对执行该
Future
的几个任务使用perf执行,结果显示大部分时间都花在memmove
上。
而且,我用一些基准进行了测试,很明显,处理Future
的时间随着缓冲区的大小而增长,即O(BUFFER_SIZE)
(注意缓冲区初始化不涉及归零操作)。
我知道,通常人们希望保持
Future
尽可能小,并且通过堆分配(例如,Vec
),移动变得便宜。然而,这更像是一个玩具示例,我对解决方案不感兴趣,而是对其背后的原因感兴趣。
特别是:
Pin
ned(用于轮询),Future
就无法取消固定(!Unpin
)?Future
?