Description
I've noticed exponential increases in compilation times as I nest async closures within each other.
I tried this code:
macro_rules! compose_middleware_inner {
( $route:ident, $first:ident, $second:ident, $($tail:ident), +) => {
$first(|| async {
compose_middleware_inner!($route, $second, $($tail),+)
}).await
};
( $route: ident, $first:ident, $second:ident ) => {
$first(|| async move { $second($route).await }).await
};
}
macro_rules! compose_middleware {
( $name:ident, $($tail:ident), +) => {
pub async fn $name<N, Fut>(route: N)
where
N: FnOnce() -> Fut,
Fut: std::future::Future<Output = ()>,
{
compose_middleware_inner!(route, $($tail),+)
}
}
}
async fn log<N, Fut>(next: N)
where
N: FnOnce() -> Fut,
Fut: std::future::Future<Output = ()>,
{
println!("log start");
next().await;
println!("log end");
}
compose_middleware!(
my_middleware, log, log, log, log, log, log, log, log, log, log, log, log, log
);
That compose_middleware!
macro invocation generates a function that looks something like this:
pub async fn my_middleware<N, Fut>(route: N)
where
N: FnOnce() -> Fut,
Fut: Future<Output = ()>,
{
log(|| async { log(|| async { log(|| async move { log(route).await }).await }).await }).await
}
I expected to see this happen: I expected this code to build within seconds
Instead, this happened: At around 3 levels it takes less than a second to build. At around 9 levels it takes around a minute to build. At around 14 levels it takes around 10 minutes to build.
Meta
I've experienced this issue on nightly, stable, and beta. And I've sampled random nightly versions going back as far as 1.39 and still witnessed the problem. There was a good bit of variability in build times, but it generally seemed to increase exponentially.
I threw this little repo up to test the issue: https://github.com/blazzy/slow-rust-async/blob/master/src/lib.rs
I thought it might be related to this issue #72408 with nested closures. Or this issue #75992 with levels of async, but those look to be resolved.
Activity
blazzy commentedon Mar 11, 2021
cargo --release
builds everything a lot faster(edit: <- this was wrong), but it still has the exponential time increase issue.I also tried making a nested async version without the generic functions.
And I tried making a nested generic function version without the async:
Both of these versions compiled very quickly and don't exhibit the exponential problem.
tmandry commentedon Mar 12, 2021
Release mode being faster suggests that the slowdown is happening after MIR optimization somewhere.
Possible duplicate of #75992 or #54540.
tmandry commentedon Mar 12, 2021
I closed those other issues, so we can keep this issue open. (Those issues might still be a good source of test cases / benchmarks.)
async
are added in Rust 1.46 #75992blazzy commentedon Mar 13, 2021
Sorry, the
--release
builds don't actually seem to be faster. I was getting less rigorous in keeping track of what I was measuring as I multi-tasked and waited for minutes long builds to complete.I made a few
cargo rustc -- -Zself-profile
runs. It's consistently dominated bymir_borrowck
andcheck_mod_privacy
.Folded here is an example summary:
hameerabbasi commentedon Mar 16, 2021
Bisected (almost certainly introduced with the original
await
PR), when this first compiled.JohnTitor commentedon Mar 16, 2021
Assigning
P-medium
as discussed as part of the Prioritization Working Group procedure and removingI-prioritize
.3 remaining items