-
Notifications
You must be signed in to change notification settings - Fork 641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Quadratic complexity in FuturesUnordered #2526
Comments
This is due to the compatibility with the cooperative scheduling of tokio (used in Mutex). This can be avoided by using async fn main() {
for n in [10_000, 20_000, 40_000, 80_000, 160_000] {
- benchmark(n).await;
+ tokio::task::unconstrained(benchmark(n)).await;
}
} Before:
After
See #2053 for more. |
Ohh I didn't know tokio does that, so my understanding is:
thanks, wow that's dangerous. Using use futures::stream::futures_unordered::FuturesUnordered;
use futures::stream::StreamExt;
use std::sync::Arc;
use tokio::sync::Mutex;
async fn do_task(mutex: Arc<Mutex<()>>) {
mutex.lock().await;
}
async fn benchmark(n: usize) {
let start_time: std::time::Instant = std::time::Instant::now();
let mutex: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
let mutex_guard = mutex.lock().await;
let mut futs = Vec::new();
for _ in 0..n {
futs.push(do_task(mutex.clone()));
}
let mut futs_unordered: FuturesUnordered<_> = futs.into_iter().collect();
std::mem::drop(mutex_guard);
for _ in 0..n {
futs_unordered.select_next_some().await;
tokio::task::yield_now().await;
}
println!("n: {}, time: {}ms", n, start_time.elapsed().as_millis());
}
#[tokio::main]
async fn main() {
for n in [10_000, 20_000, 40_000, 80_000, 160_000] {
benchmark(n).await;
}
}
|
I found a way to mitigate this on the FuturesUnordered side: #2527 After #2527, the benchmark result became:
A little slower than the version using unconstrained, but a little faster than the version using tokio::spawn, on my machine. |
There might be a performance bug in
FuturesUnordered
.Here's an example, which puts
n
async futures inFuturesUnordered
and waits for all of them to finish.Each task locks an async mutex and immediately frees it, which causes them to finish one by one.
It looks like the execution time is quadratic, each time
n
increases 2x, the time increases 4x.Documentation states:
and the implementation tries hard to avoid quadratic complexity, so it looks like a bug.
We ran into this issue when trying to perform many concurrent database queries In scylladb/scylla-rust-driver#362
main.rs
:Cargo.toml
:Doing this using
tokio::spawn
makes the time linear again:main.rs
The text was updated successfully, but these errors were encountered: