Skip to content

rebinding values in an async function can double the memory usage #96084

Open
@jkarneges

Description

@jkarneges
Contributor

Rebinding a value within an async function can cause the generated future to require twice the memory for the value. This seems to only happen if the value was borrowed earlier:

use std::mem;

async fn foo() {
    let x = [0u8; 100];
    async {}.await;
    println!("{}", x.len());
}

async fn a() {
    let fut = foo();
    let fut = fut;
    fut.await;
}

async fn b() {
    let fut = foo();
    println!("{}", mem::size_of_val(&fut));
    let fut = fut;
    fut.await;
}

fn main() {
    assert_eq!(mem::size_of_val(&foo()), 102);

    // 1 + sizeof(foo)
    assert_eq!(mem::size_of_val(&a()), 103);

    // 1 + (sizeof(foo) * 2)
    assert_eq!(mem::size_of_val(&b()), 205);
}

Playground link.

I don't know if this counts as a bug but the behavior is a bit surprising.

The effect is present in both debug and release modes with rustc 1.60.0.

Activity

eholk

eholk commented on Apr 15, 2022

@eholk
Contributor

@rustbot label A-async-await AsyncAwait-Polish

tmandry

tmandry commented on Apr 22, 2022

@tmandry
Member

Hi! This looks related to #62321 and #61849 but not exactly the same as either of those. It might be easier to solve.

eholk

eholk commented on Apr 25, 2022

@eholk
Contributor

We discussed this in triage today. It seems like the issue is probably that MaybeBorrowedLocals is being too conservative. Once it finds a borrowed local, it stays live until it finds a StorageDead instruction, and these get inserted at the end of the scope. It seems like once we move out of a local, we should be able to mark that as StorageDead, instead of waiting until the end of the scope, which would probably fix this issue.

@rustbot label +AsyncAwait-Triaged

added
AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
on Apr 25, 2022
tmandry

tmandry commented on Apr 25, 2022

@tmandry
Member

In #60187 I think I attempted making a MIR building change that caused StorageDead to be emitted earlier. The performance hit was unacceptable for doing this for all functions (benchmark results), but there was discussion about doing this for only generators. I can't remember where exactly we landed on that, but I don't see a diff in the MIR building code of the final PR. So maybe that's the thread to pick up again from here.

moved this to On deck in wg-async workon Dec 8, 2022

1 remaining item

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitAsyncAwait-PolishAsync-await issues that are part of the "polish" areaAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.

    Type

    No type

    Projects

    Status

    On deck

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @eholk@jkarneges@tmandry@rustbot

      Issue actions

        rebinding values in an async function can double the memory usage · Issue #96084 · rust-lang/rust