Paralelismo de Dados com Rayon
Rayon transforma iteradores sequenciais em iteradores paralelos com um pool de threads de roubo de trabalho (work-stealing). Ele se destaca no processamento de dados limitado pela CPU sem gerenciar threads manualmente.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
use rayon::prelude::*;
fn main() {
let sum: i32 = (1..=1_000_000).into_par_iter().sum();
println!("{sum}");
}Quando usar isso: Mapas paralelos, filtros, reduções e ordenação em coleções na memória.
Exemplo de Trabalho
use rayon::prelude::*;
#[derive(Debug)]
struct Record {
id: u64,
value: f64,
}
fn main() {
let records: Vec<Record> = (0..100_000)
.map(|i| Record {
id: i,
value: (i as f64).sqrt(),
})
.collect();
let top: Vec<_> = records
.par_iter()
.filter(|r| r.value > 100.0)
.map(|r| r.id)
.collect();
let total: f64 = records.par_iter().map(|r| r.value).sum();
println!("matches = {}, total = {:.2}", top.len(), total);
}O que isso demonstra:
par_iterdivide o trabalho entre o pool do Rayon.- Combinadores espelham iteradores sequenciais.
- Os resultados são determinísticos para reduções associativas como
sum.
Mergulho Profundo
Como Funciona
- Roubo de trabalho (Work stealing): Threads ociosas roubam tarefas de filas ocupadas - bom balanceamento de carga em trabalhos desiguais.
- Pool Global: Pool padrão dimensionado para núcleos da CPU. Inicialize uma vez por processo.
- Dividir e Conquistar:
par_iterdivide intervalos até que os pedaços sejam pequenos o suficiente. - Restrição de
Send: Itens e closures devem serSend- o mesmo quethread::spawn.
APIs Comuns
| Método | Propósito |
|---|---|
par_iter() | Iteração paralela imutável |
par_iter_mut() | Mutação paralela no local |
into_par_iter() | Consumir coleção em paralelo |
par_sort / par_sort_unstable | Ordenação paralela em slices |
Notas de Rust
// Pool de threads personalizado para isolamento
use rayon::ThreadPoolBuilder;
let pool = ThreadPoolBuilder::new().num_threads(4).build().unwrap();
pool.install(|| {
let result = data.par_iter().map(expensive).sum::<i32>();
});- Use
pool.installpara definir o escopo de pools personalizados. - Evite I/O dentro de closures
par_iter- bloqueia threads do pool. ParallelIteratorrequerSendparaTe closures.
Armadilhas
- Coleções Pequenas: O overhead de thread excede a economia em vetores de 10 itens. Correção: Use iteradores sequenciais abaixo de um limite (geralmente milhares de itens).
- I/O Bloqueante em Closures Paralelas: Deixa o pool Rayon faminto. Correção: Use I/O assíncrono ou um pool de bloqueio dedicado (
spawn_blocking). - Reduções Não Associativas:
sumem floats ou operações personalizadas podem variar dependendo da ordem. Correção: Use redução sequencial ou soma compensada. - Mutex dentro de
par_iter: Serializa o trabalho, derrota o paralelismo. Correção: Usemappara produzir valores e depoisreduce, ou dobre por partição. - Compartilhamento de
Rcentrepar_iter:Rcnão éSend. Correção: UseArcou processe dados de propriedade por item.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
thread::scope | Chunking manual com empréstimos | Você quer a ergonomia do iterador |
| Iteradores Sequenciais | Dados pequenos ou trabalho barato por item | Limitado pela CPU em grandes coleções |
tokio::task::spawn_blocking | Trabalho de CPU em aplicativos assíncronos | Tarefas em lote puramente síncronas |
| Crates SIMD / GPU | Núcleos numéricos em escala extrema | Mapas de lógica de negócios gerais |
FAQs
Quando o Rayon ajuda?
Trabalho limitado pela CPU em grandes dados na memória com itens independentes - análise, transformações, analytics.
A ordem importa?
A ordem de collect corresponde à ordem da fonte. reduce pode usar uma ordem de árvore diferente - use operações associativas.
Rayon com async?
Chame Rayon de spawn_blocking no Tokio - nunca bloqueie o executor assíncrono em par_iter diretamente em caminhos críticos.
Contagem de threads personalizada?
ThreadPoolBuilder::num_threads(n) - útil para deixar núcleos para outros serviços.
par_iter_mut?
Modifique elementos em paralelo quando cada índice for independente - sem violações de alias.
Paralelismo aninhado?
Rayon detecta par_iter aninhado e evita a supersubscrição em muitos casos - ainda assim, faça o profiling.
Erros em map paralelo?
Colete Result com try_reduce ou mapeie para Result e depois use short-circuit com um fold personalizado.
Desempenho de ordenação?
par_sort_unstable é rápido em slices grandes - certifique-se de que Ord seja barato.
Relação com Polars?
Polars usa seu próprio motor paralelo - use Rayon para pipelines Rust personalizados fora do Polars.
Depuração do Rayon?
RAYON_NUM_THREADS=1 força o comportamento sequencial para reproduzir bugs lógicos sem corridas (race conditions).
Relacionado
- Noções Básicas de Concorrência - threads manuais
- Threads com Escopo - paralelismo baseado em empréstimos
- Bloqueio em Async - Rayon em aplicativos async
- Send & Sync - requisito de
Send - Melhores Práticas de Concorrência - perfil antes de paralelizar
Versões da Stack: 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+.