·2 min de leitura
construindo um bus de eventos de alta performance em rust
rustperformancebackend
Ao construir sistemas em tempo real, a contenção de locks em dispatchers de eventos rapidamente vira um gargalo. Neste post, vamos examinar o design de um ring buffer lock-free em Rust.
Visão geral da arquitetura
Aqui está o fluxo de eventos de alto nível entre as threads produtoras e consumidoras:
[ Produtor 1 ] ──┐
├──> [ Ring Buffer (Head/Tail Atômico) ] ──> [ Worker Consumidor ]
[ Produtor 2 ] ──┘
Comparação Go vs Rust
Fizemos o benchmark de uma implementação de channel em Go contra uma fila lock-free em Rust processando 10 milhões de eventos:
| Implementação | Alocações | Latência P99 | Throughput (ops/s) |
|---|---|---|---|
| Go Channels | 10.000.000 | 1.45 ms | 6.8M |
| Rust Mutex | 0 | 0.82 ms | 12.1M |
| Rust Lock-Free | 0 | 0.11 ms | 48.5M |
Trecho da implementação em Rust
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct EventBus<T, const N: usize> {
buffer: [Option<T>; N],
head: AtomicUsize,
tail: AtomicUsize,
}
impl<T: Copy, const N: usize> EventBus<T, N> {
pub fn push(&self, value: T) -> Result<(), T> {
let current_tail = self.tail.load(Ordering::Relaxed);
let next_tail = (current_tail + 1) % N;
if next_tail == self.head.load(Ordering::Acquire) {
return Err(value); // Fila cheia
}
self.tail.store(next_tail, Ordering::Release);
Ok(())
}
}
Principais conclusões
- Prefira
AtomicUsizea mutexes quando a capacidade da fila for limitada e conhecida previamente. - Evite alocações na heap no hot path pré-alocando os slots do ring buffer.
- Faça o benchmark em modo release para permitir que o LLVM faça inlining de operações atômicas.