Eugene-Usachev
@Eugene-Usachev

Почему асинхронный блок поверх асинхронного асинхронного блока удваивает память?

Я пишу свой асинхронный движок. Я захотел предоставить пользователю более удобный интерфейс, для этого мне потребовалось дополнить его Future. Условно, он отдаёт мне Future, а я делаю что-то вроде
let mut res = MaybeUninit::uninit();
let res_ptr: *mut T = res.as_mut_ptr();
self.exec_future(async move {
     unsafe { res_ptr.write(future.await) };
});
unsafe { res.assume_init() }


Попробовал так сделать, и обнаружил, что Future, который был создан, стоит в два раза больше. Я поставил несколько "экспериментов" и не обнаружил причин для этого.

Вот код для демонстрации (можете не запускать, я оставил комментарии после println)
let block= async {
            let a = [0u8;8192];
            yield_now().await;
            black_box(println!("{}", a.len()));
            0
        };

        #[inline(never)]
        async fn a() -> usize {
            let a = [0u8;8192];
            yield_now().await;
            black_box(println!("{}", a.len()));
            0
        }

        let size_of_future_from_block = mem::size_of_val(&block);
        println!("size_of_future_from_block: {}", size_of_future_from_block); // 8194
        let size_of_future_from_fn = mem::size_of_val(&a());
        println!("size_of_future_from_fn: {}", size_of_future_from_fn); // 8194

        let closure_from_future_from_block = async {
            block.await
        };
        let size_of_closure_from_future_from_block = mem::size_of_val(&closure_from_future_from_block);
        println!("size_of_closure_from_future_from_block: {}", size_of_closure_from_future_from_block); // 16389

        let closure_from_future_from_fn = async {
            a().await
        };
        let size_of_closure_from_future_from_fn = mem::size_of_val(&closure_from_future_from_fn);
        println!("size_of_closure_from_future_from_fn: {}", size_of_closure_from_future_from_fn); // 8195


Я запустил этот тест в release, если это важно.

UDP: я решил эту проблему для себя. Для этого достаточно создать вспомогательную структуру, которая реализует Future. Тогда память не будет удваиваться. Для случая выше, это могло выглядеть как:
struct WithRes<R, Fut: Future<Output=R>> {
    res_ptr: *mut R,
    fut: Fut
}

impl<R, Fut: Future<Output=R>> WithRes<R, Fut> {
    fn new(res_ptr: *mut R, fut: Fut) -> Self {
        Self { res_ptr, fut }
    }
}

impl<R, Fut: Future<Output=R>> Future for WithRes<R, Fut> {
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = unsafe { self.get_unchecked_mut() };
        let mut pinned = unsafe { Pin::new_unchecked(&mut this.fut) };
        match pinned.as_mut().poll(cx) {
            Poll::Pending => Poll::Pending,
            Poll::Ready(res) => {
                unsafe { this.res_ptr.write(res) };
                Poll::Ready(())
            }
        }
    }
}
  • Вопрос задан
  • 116 просмотров
Решения вопроса 1
Eugene-Usachev
@Eugene-Usachev Автор вопроса
Я поднял этот вопрос на официальном форуме. Мне ответили, что пока что в async Rust есть несколько мест, в которых память используется неэффективно. Это будет исправлено в скором времени.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы