Abstrações de Custo Zero
Custo zero significa que você não paga por abstrações que não usa, e as abstrações que você usa podem compilar para código tão eficiente quanto loops escritos manualmente.
Receita
let total: u32 = data.iter().filter(|x| x % 2 == 0).map(|x| x * 2).sum();Quando usar isso: Escolher entre iteradores, genéricos e loops manuais sem sacrificar a clareza.
Exemplo de Trabalho
fn sum_even_double(slice: &[u32]) -> u32 {
slice.iter().filter(|n| *n % 2 == 0).map(|n| n * 2).sum()
}
fn sum_even_double_loop(slice: &[u32]) -> u32 {
let mut acc = 0;
for &n in slice {
if n % 2 == 0 {
acc += n * 2;
}
}
acc
}Inspecione o assembly otimizado em --release; ambos frequentemente compilam para código de máquina idêntico.
O que isso demonstra:
- Adaptadores de iterador se fundem em um único loop com LLVM
- Genéricos são monomorfizados para código especializado por tipo
Option/Resultmatchcompila sem alocação de heap- Prefira código expressivo até que a profilagem prove o contrário
Análise Profunda
O Que é Compilado e Descartado
| Abstração | Custo |
|---|---|
| Cadeia de iteradores | Frequentemente o mesmo que um loop |
Option/Result | Apenas na pilha, sem boxing por padrão |
fn genérico | Monomorfizado, embutido (inlined) |
enum dispatch | União marcada (tagged union), sem vtable a menos que dyn |
Quando Há Custo
dyn Traitdispatch dinâmicoBox<dyn Error>em caminhos críticos (hot path)- Closures capturando
String(alocação + chamada indireta se não for embutida) - Asserções de depuração apenas em builds de depuração
Armadilhas
- Assumir que iteradores são sempre de custo zero - closures complexas podem não ser embutidas. Correção: inspecione o codegen ou simplifique capturas.
collect()sem dica de tipo - passes extras ou alocações. Correção: usesum(),try_fold, ou tipo de destino explícito.dynpor conveniência - vtable por chamada. Correção: genéricos ouenumdispatch em caminhos críticos.- Depuração de iteradores em debug - percorrer a cadeia de adaptadores é difícil. Correção: expanda para loop temporariamente apenas durante a depuração.
- LTO desativado em release - inlining entre crates falha. Correção: LTO fino no perfil de release.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Loop for manual | Hotspot comprovado precisa de controle | Escolha padrão em vez de iteradores |
Iteradores paralelos rayon | Dados bound por CPU em paralelo | Código de I/O assíncrono |
no_std | Embarcado | Pilha completa Tokio/Axum |
FAQs
Performance de iterador vs loop?
Geralmente igual em release; benchmark se for crítico.
Option aloca?
Não para Option<T> onde T tem tamanho fixo; é uma otimização de nicho na pilha.
Custo da monomorfização?
Tempo de compilação e tamanho do binário crescem com muitas instanciações genéricas; o custo por chamada é dispatch estático.
Closures são de custo zero?
Quando embutidas, sim; capturar o ambiente pode alocar e impedir o embutimento.
Retorno `impl Trait`?
Dispatch estático, custo zero como tipo de retorno concreto quando não é um objeto de trait.
async é de custo zero?
Máquina de estados na pilha/heap por tarefa; não é grátis, mas não há alocação de heap por poll em futures bem escritas.
Comparar no Godbolt?
Cole o IR LLVM do rustc ou o assembly para duas versões lado a lado.
Iterador Vec vs indexação?
Frequentemente idêntico; verificações de limites podem ser otimizadas com iteradores.
Polars lazy é de custo zero?
O plano de consulta otimiza pipelines; collect materializa - a camada de plano é uma abstração lazy.
Quando usar dyn intencionalmente?
Limites de plugin, FFI, ou caminhos raros e frios onde o tamanho do binário é mais importante que a velocidade de chamada.
Relacionado
- Noções Básicas de Performance - meça primeiro
- Evitando Clones - propriedade
- SIMD & Vetorização - SIMD explícito
- 30 Regras de Performance - lista de regras
Versões da Pilha: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.