Perfilando código Rust: encontrando gargalos e acelerando programas

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que perfilar código Rust?

Rust é famoso por sua velocidade e segurança, mas mesmo o código mais rápido pode ficar lento devido a gargalos não óbvios. A criação de perfil é o processo de medir o tempo de execução, uso de memória e outros recursos, que ajuda a encontrar "gargalos". Sem a criação de perfil, a otimização se torna um palpite: você pode "melhorar" partes do código que não afetam o desempenho.

Neste artigo, abordaremos as principais ferramentas de criação de perfil para Rust: perf, flamegraph, cargo-flamegraph e o benchmarking integrado. Você aprenderá como encontrar funções lentas, alocações e cache misses, além de como interpretar os dados obtidos.

Ferramentas de criação de perfil Rust: escolhendo a abordagem correta

1. Benchmarking integrado com #[bench]

O primeiro passo para a criação de perfil é escrever benchmarks. O Rust possui suporte integrado para benchmarks (instável, requer nightly). Exemplo:

// No arquivo benches/my_bench.rs
#![feature(test)]
extern crate test;

use test::Bencher;

#[bench]
fn bench_my_function(b: &mut Bencher) {
    let data = vec![1, 2, 3, 4, 5];
    b.iter(|| {
        // Função sendo testada
        let _ = data.iter().sum::<i32>();
    });
}

Execução: cargo bench. Você obterá medições precisas de tempo, mas isso não mostrará exatamente onde o tempo é gasto dentro da função.

2. perf — criador de perfil em nível de kernel

A ferramenta perf (Linux) coleta amostras de contadores da CPU. Ela mostra quais linhas de código são executadas com mais frequência.

Instalação: sudo apt install linux-tools-common linux-tools-generic.

Exemplo de uso:

# Compilar com símbolos de depuração
cargo build --release

# Executar a criação de perfil
sudo perf record ./target/release/my_program

# Visualizar o relatório
sudo perf report

No relatório, você verá as funções ordenadas pela porcentagem de tempo de execução. Este é o primeiro candidato para otimização.

3. flamegraph — visualização da pilha de chamadas

Flamegraph é um diagrama SVG onde a largura de cada retângulo corresponde ao tempo de execução da função. Quanto mais largo o retângulo, mais tempo é gasto nessa função ou em suas chamadas filhas.

Instalação do cargo-flamegraph:

cargo install flamegraph

Execução:

cargo flamegraph --bin my_program

O resultado é o arquivo flamegraph.svg. Abra-o no navegador. Procure por "platôs" largos — estes são os gargalos.

Exemplo prático: encontrando um gargalo no código Rust

Considere um programa simples que processa um grande array de dados:

fn process(data: &[u8]) -> Vec<u8> {
    let mut result = Vec::with_capacity(data.len());
    for &byte in data {
        result.push(byte.wrapping_mul(2));
    }
    result
}

fn main() {
    let data = vec![0u8; 10_000_000];
    let _ = process(&data);
}

Vamos compilar e executar a criação de perfil:

cargo build --release
cargo flamegraph --bin my_program

Ao abrir o flamegraph, vemos que a maior parte do tempo é gasta no loop for e na chamada wrapping_mul. Observe que Vec::push pode causar realocações se a capacidade não for calculada com precisão. No nosso caso, with_capacity está definido corretamente, mas o próprio loop é o gargalo.

Otimização: use iteradores e map:

fn process_optimized(data: &[u8]) -> Vec<u8> {
    data.iter().map(|&b| b.wrapping_mul(2)).collect()
}

Uma nova medição mostra uma aceleração de 10-15% devido à vetorização (autovetorização do LLVM).

Técnicas avançadas: al

Blogs

Book Recommendations