← posts
·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

  1. Prefira AtomicUsize a mutexes quando a capacidade da fila for limitada e conhecida previamente.
  2. Evite alocações na heap no hot path pré-alocando os slots do ring buffer.
  3. Faça o benchmark em modo release para permitir que o LLVM faça inlining de operações atômicas.