Introdução: por que Rust já é rápido, mas não é ideal
Rust é tradicionalmente posicionado como uma linguagem que oferece desempenho de C++ com segurança de memória. No entanto, "rápido por padrão" é um mito. Mesmo em Rust, é possível escrever código lento: clonagem excessiva, estruturas de dados não otimizadas, uso incorreto de alocações. Neste artigo, vamos abordar métodos práticos que ajudarão a extrair o máximo da sua aplicação Rust, sem sacrificar a segurança.
Vamos considerar quatro aspectos principais: profiling, zero-cost abstractions, gerenciamento de memória e paralelismo. Cada seção contém exemplos reais de código e resultados mensuráveis.
1. Profiling: não adivinhe, meça
Sem profiling, qualquer otimização é um tiro no escuro. Rust possui várias ferramentas poderosas.
1.1. Benchmarks integrados com cargo bench
Para micro-otimizações, use #![feature(test)] e o módulo test. Exemplo:
#![feature(test)]extern crate test;
pub fn slow_sum(data: &[u64]) -> u64 { data.iter().sum()}
#[cfg(test)]mod benchmarks { use super::*; use test::Bencher;
#[bench] fn bench_slow_sum(b: &mut Bencher) { let data: Vec<u64> = (0..1000).collect(); b.iter(|| slow_sum(&data)); }}Execução: cargo bench. Você verá o tempo exato de execução e o número de iterações.
1.2. Profilers externos: perf e flamegraph
Para encontrar gargalos em aplicações grandes, use perf no Linux ou DTrace no macOS. Instale a ferramenta de visualização:
cargo install flamegraphcargo flamegraph --bin your_appIsso criará um gráfico SVG, onde cada "língua de fogo" é uma pilha de chamadas. Quanto mais larga a chama, mais tempo é gasto nessa função.
Dica: Sempre faça profiling no modo release: cargo build --release. O modo debug adiciona verificações de limites e informações de depuração, distorcendo os resultados.
2. Zero-cost abstractions: use, mas com sabedoria
Rust se orgulha de que abstrações de alto nível não adicionam custos extras. Mas isso funciona apenas quando usado corretamente.
2.1. Iteradores vs loops
Iteradores em Rust são compilados para o mesmo código de máquina que loops manuais. Exemplo:
// Ruim: loop manual com índicesfn manual_sum(data: &[u64]) -> u64 { let mut sum = 0; for i in 0..data.len() { sum += data[i]; } sum}
// Bom: iterador (zero-cost)fn iter_sum(data: &[u64]) -> u64 { data.iter().sum()}Benchmarks mostram desempenho idêntico. Mas iteradores são mais seguros (sem verificação manual de limites) e mais legíveis.
2.2. Evite alocações desnecessárias
Cada chamada a Vec::new() ou String::new() é uma alocação no heap. Use arrays na pilha sempre que possível:
// Ruim: alocação a cada chamadafn process_items(items: &[u32]) -> Vec<u32> { items.iter().map(|x| x * 2).collect()}
// Bom: reutilização de bufferfn process_items_inplace(items: &mut [u32]) { for item in items.iter_mut() { *item *= 2; }}Se a alocação for inevitável, use Vec::with_capacity(n) para evitar múltiplas realocações.
3. Gerenciamento de memória: propriedade e clonagem
O sistema de propriedade do Rust é a principal ferramenta de otimização. Ele permite evitar a coleta de lixo, mas exige uma abordagem consciente.
3.1. Empréstimo vs clonagem
Cada .clone() é uma cópia de dados. Tente usar referências:
// Ruim: clonagem da string inteirafn greet(name: String) -> String { format!("Hello, {