Course contentsShow
← Rust
Lesson 1180 of 1,91010. Concurrency PrimitivesFree lesson

Profiling Async Task Contention

You will identify performance bottlenecks in task scheduling and context switching.

In this lesson, you will learn how to identify when your async tasks are fighting for resources rather than executing useful work, a phenomenon known as task contention.

The Cost of Task Contention

In an async runtime, your tasks are multiplexed across a finite pool of OS threads. Performance bottlenecks often occur not because of "slow code," but because tasks are constantly being context-switched or blocked waiting for a turn on the executor's thread.

Task contention manifests in two primary ways:

  1. Executor Starvation: A few heavy or long-running tasks occupy the worker threads, preventing other tasks from being polled.
  2. Waker Flooding: Tasks are waking up excessively, forcing the executor to constantly reschedule them, leading to high CPU usage in the runtime machinery rather than your application logic.

Identifying the Bottleneck

To profile this, you must look at executor metrics rather than just standard CPU time. If you use tokio-console, you can observe the "poll duration" and "scheduled duration" of your tasks.

Consider this scenario:

// A "noisy" task that yields too frequently
async fn noisy_task() {
    loop {
        do_work();
        // If this yield happens too often, the task overhead 
        // exceeds the useful work duration.
        tokio::task::yield_now().await;
    }
}

If the ratio of scheduled duration (time spent waiting in the queue) to poll duration (time spent actually running) is high, your system is suffering from contention. This often happens when you have thousands of tasks fighting for a handful of threads, causing the runtime's internal lock-free queues to become a point of serial access contention.

When profiling, look for "long tails" in your latency histograms. If your task execution time is low but the latency is high, your tasks are being queued behind other work. This confirms that the bottleneck is the scheduler throughput, not your algorithmic complexity. To mitigate this, consider reducing task granularity or using spawn_blocking for CPU-intensive chunks to prevent them from pinning your async threads.

Key Takeaway: High task contention is detected by comparing the time tasks spend waiting in the executor queue versus the time spent actively polling, indicating that your concurrency model is effectively becoming serialized by the runtime scheduler.