Skip to main content
Tokio is designed for high-performance async I/O. This guide covers key optimization strategies and runtime configuration for production workloads.

Runtime Configuration

Worker Threads

By default, Tokio spawns one worker thread per CPU core. You can customize this:
Environment Variable Override:
Start with the default (number of CPU cores) and only adjust if profiling shows thread contention or underutilization.

Choosing the Right Scheduler

Current-Thread Scheduler

Single-threaded, runs all tasks on the current thread:
Use when:
  • You have a small number of tasks
  • Tasks are primarily I/O-bound
  • You want minimal overhead
  • Your application is single-threaded

Multi-Thread Scheduler

Work-stealing scheduler that distributes tasks across multiple threads:
Use when:
  • You have many concurrent tasks
  • You want to utilize multiple CPU cores
  • Tasks have varying execution times
  • Building a high-throughput server
The multi-thread scheduler is required for block_in_place. The current-thread scheduler will panic if you call block_in_place.

Task Spawning Optimization

Minimize Task Creation Overhead

Spawning tasks has overhead. Batch operations when possible:

Use spawn_local for !Send Types

When tasks don’t need to move between threads:

I/O Optimization

Buffer Sizes

Use appropriate buffer sizes for your workload:
Common buffer sizes: 4 KB for small messages, 16-64 KB for bulk transfers, 8 KB for HTTP.

Vectored I/O

Use vectored I/O for scatter/gather operations:

Split Sockets

Split sockets for concurrent reads and writes:

Channel Selection and Tuning

Choosing the Right Channel

Bounded vs Unbounded Channels

Use unbounded channels only when you’re certain the producer won’t overwhelm the consumer. Bounded channels provide backpressure.

Channel Capacity Sizing

Blocking Operations

Thread Pool Configuration

Configure the blocking thread pool size:
Blocking threads are spawned on-demand and kept alive for the configured duration when idle.

Limit Concurrent Blocking Operations

Use a semaphore to limit concurrent blocking operations:

Consider Dedicated Thread Pools

For CPU-bound work, use a dedicated thread pool like rayon:

Memory Management

Reuse Buffers

Use object pools for frequently allocated buffers:

Use bytes Crate

The bytes crate provides efficient buffer types:

Time and Timers

Batch Timers

Avoid creating too many individual timers:

Timer Resolution

Tokio’s timer resolution is typically 1ms:

Monitoring and Metrics

Enable Runtime Metrics

Poll Count Histogram (Unstable)

.cargo/config.toml

Event Loop Configuration

Event Interval

Control how often I/O events are processed:
Lower values increase responsiveness but may reduce throughput. Higher values batch more work but increase latency.

Global Queue Interval

Control work-stealing behavior:

Profiling and Debugging

Using tokio-console

Enable console subscriber for task debugging:
Cargo.toml
Run tokio-console to visualize task execution.

Trace Events (Unstable)

Cargo.toml
.cargo/config.toml

Performance Checklist

  • Use the multi-thread scheduler for high-throughput applications
  • Configure appropriate buffer sizes (4-64 KB)
  • Use bounded channels with appropriate capacity
  • Limit concurrent blocking operations with semaphores
  • Reuse buffers instead of allocating new ones
  • Use spawn_blocking for blocking I/O, not CPU-bound work
  • Consider dedicated thread pools (rayon) for CPU-intensive tasks
  • Batch timer operations with interval
  • Profile with tokio-console to identify bottlenecks
  • Use bytes crate for efficient buffer management
  • Split sockets for concurrent read/write operations
  • Apply backpressure with bounded channels and semaphores
Always profile before and after optimizations. Use tools like tokio-console, perf, and flamegraph to identify actual bottlenecks.