Introdução: por que Rust já é rápido, mas este não é o limite
\n\nRust é merecidamente considerado uma das linguagens de programação de sistema mais performáticas. Graças às abstrações de custo zero (zero-cost abstractions) e ao sistema de propriedade rigoroso, o código em Rust geralmente é tão rápido quanto o código equivalente em C ou C++. No entanto, mesmo em Rust existem "armadilhas" que podem tornar a aplicação mais lenta, especialmente para desenvolvedores iniciantes.
\n\nNeste artigo, vamos analisar os princípios-chave e as técnicas práticas de otimização de aplicações Rust. Você aprenderá como criar perfis de código, evitar alocações desnecessárias, trabalhar eficientemente com coleções e usar o poder do compilador. O material é voltado para iniciantes, portanto, todos os exemplos serão o mais simples e claros possível.
\n\nPronto para tornar seu código Rust ainda mais rápido? Vamos lá!
\n\n1. Criação de perfil: primeiro medimos, depois otimizamos
\n\nAntes de otimizar qualquer coisa, é preciso entender o que está causando lentidão. Em Rust, existem ferramentas excelentes para isso.
\n\n1.1. Benchmarks com cargo bench
\n\nA maneira padrão é usar benchmarks. Eles permitem medir o tempo de execução de funções específicas.
\n\n// No arquivo benches/my_bench.rs\nuse criterion::{criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 1,\n 1 => 1,\n n => fibonacci(n-1) + fibonacci(n-2),\n }\n}\n\nfn bench_fib(c: &mut Criterion) {\n c.bench_function("fib 20", |b| b.iter(|| fibonacci(20)));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nPara executar, adicione ao Cargo.toml:
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_bench"\nharness = false\n\nEm seguida, execute cargo bench. Você obterá números precisos: tempo de execução, desvio padrão e comparação com execuções anteriores.
1.2. Criação de perfil de CPU e memória
\n\nPara uma análise mais aprofundada, use:
\n- \n
- perf (Linux) — criador de perfil em nível de kernel. \n
- Valgrind — para encontrar vazamentos de memória e analisar o cache. \n
- flamegraph — visualização dos "pontos críticos" do código. \n
Exemplo de geração de flamegraph:
\ncargo install flamegraph\ncargo flamegraph --bin my_app\n# Abra flamegraph.svg no navegador\n\n\nO Flamegraph mostrará quais funções consomem mais tempo. Isso ajudará a focar no que é mais importante.
\n\n2. Gerenciamento de memória: evitando alocações desnecessárias
\n\nAlocações no heap são uma das causas mais comuns de código lento. Rust oferece controle total sobre a memória, mas iniciantes frequentemente abusam de Box, Vec e String.
2.1. Use a pilha em vez do heap
\n\nSempre que possível, armazene dados na pilha. Por exemplo, em vez de Vec para um conjunto fixo pequeno, use um array:
// Lento: alocação no heap\nlet data: Vec<i32> = vec![1, 2, 3, 4, 5];\n\n// Rápido: pilha\nlet data: [i32; 5] = [1, 2, 3, 4, 5];\n\n\n2.2. Pré-alocação de memória
\n\nSe o tamanho da coleção for conhecido antecipadamente, use with_capacity:
// Ruim: muitas realocações\nlet mut v = Vec::new();\nfor i in 0..1000 {\n v.push(i);\n}\n\n// Bom: uma alocação\nlet mut v = Vec::with_capacity(1000);\nfor i in 0..1000 {\n v.push(i);\n}\n\n\n2.3. Evite clonagem
\n\nClonar strings e estruturas grandes é uma operação cara. Use empréstimo (&str em vez de String):
fn process(s: &str) { /* ... */ }\n\n// Ruim: clonagem\nlet s = String::from("hello");\nprocess(&s.clone());\n\n// Bom: empréstimo\nprocess(&s);\n\n\n