Skip to content

Commit 4ef4564

Browse files
committed
Reduce memory footprint
By creating the future manually instead of relying on `async { .. }`, we workaround rustc's inefficient future layouting. On [a simple benchmark](https://github.com/hez2010/async-runtimes-benchmarks-2024) spawning 1M of tasks, this reduces memory use from about 512 bytes per future to about 340 bytes per future. More context: hez2010/async-runtimes-benchmarks-2024#1
1 parent ea9e6e4 commit 4ef4564

File tree

2 files changed

+31
-5
lines changed

2 files changed

+31
-5
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ async-task = "4.4.0"
2323
concurrent-queue = "2.5.0"
2424
fastrand = "2.0.0"
2525
futures-lite = { version = "2.0.0", default-features = false }
26+
pin-project-lite = "0.2"
2627
slab = "0.4.4"
2728

2829
[target.'cfg(target_family = "wasm")'.dependencies]

src/lib.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,16 @@
4242
use std::fmt;
4343
use std::marker::PhantomData;
4444
use std::panic::{RefUnwindSafe, UnwindSafe};
45+
use std::pin::Pin;
4546
use std::rc::Rc;
4647
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
4748
use std::sync::{Arc, Mutex, MutexGuard, RwLock, TryLockError};
48-
use std::task::{Poll, Waker};
49+
use std::task::{Context, Poll, Waker};
4950

5051
use async_task::{Builder, Runnable};
5152
use concurrent_queue::ConcurrentQueue;
5253
use futures_lite::{future, prelude::*};
54+
use pin_project_lite::pin_project;
5355
use slab::Slab;
5456

5557
#[cfg(feature = "static")]
@@ -245,10 +247,7 @@ impl<'a> Executor<'a> {
245247
let entry = active.vacant_entry();
246248
let index = entry.key();
247249
let state = self.state_as_arc();
248-
let future = async move {
249-
let _guard = CallOnDrop(move || drop(state.active().try_remove(index)));
250-
future.await
251-
};
250+
let future = AsyncCallOnDrop::new(future, move || drop(state.active().try_remove(index)));
252251

253252
// Create the task and register it in the set of active tasks.
254253
//
@@ -1155,6 +1154,32 @@ impl<F: FnMut()> Drop for CallOnDrop<F> {
11551154
}
11561155
}
11571156

1157+
pin_project! {
1158+
/// A wrapper around a future, running a closure when dropped.
1159+
struct AsyncCallOnDrop<Fut, Cleanup: FnMut()> {
1160+
#[pin]
1161+
future: Fut,
1162+
cleanup: CallOnDrop<Cleanup>,
1163+
}
1164+
}
1165+
1166+
impl<Fut, Cleanup: FnMut()> AsyncCallOnDrop<Fut, Cleanup> {
1167+
fn new(future: Fut, cleanup: Cleanup) -> Self {
1168+
Self {
1169+
future,
1170+
cleanup: CallOnDrop(cleanup),
1171+
}
1172+
}
1173+
}
1174+
1175+
impl<Fut: Future, Cleanup: FnMut()> Future for AsyncCallOnDrop<Fut, Cleanup> {
1176+
type Output = Fut::Output;
1177+
1178+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1179+
self.project().future.poll(cx)
1180+
}
1181+
}
1182+
11581183
fn _ensure_send_and_sync() {
11591184
use futures_lite::future::pending;
11601185

0 commit comments

Comments
 (0)