---
title: "jemalloc vs mimalloc em Rust: Alocador Global | Rust Brasil"
url: "https://rustlang.com.br/blog/jemalloc-mimalloc-alocador-global-rust-performance-2026/"
markdown_url: "https://rustlang.com.br/blog/jemalloc-mimalloc-alocador-global-rust-performance-2026.MD"
description: "Compare jemalloc e mimalloc em Rust, configure o alocador global e meça throughput, latência, memória, CPU e RSS antes de levar a mudança para produção."
date: "2026-09-02"
author: "Equipe Rust Brasil"
---

# jemalloc vs mimalloc em Rust: Alocador Global | Rust Brasil

Compare jemalloc e mimalloc em Rust, configure o alocador global e meça throughput, latência, memória, CPU e RSS antes de levar a mudança para produção.


**Para testar `jemalloc` ou `mimalloc` em Rust, declare o candidato com `#[global_allocator]` e compare três builds: alocador padrão, jemalloc e mimalloc.** A escolha correta não vem da reputação da biblioteca, mas de medições com a carga do seu serviço. Registre throughput, latência p50/p95/p99, CPU, pico de RSS, memória depois de uma rajada e tempo de build. Se o ganho não for consistente e maior que o ruído, mantenha o padrão.

A troca costuma ser mais promissora em serviços multithread com muitas alocações pequenas, filas, mapas, parsing, serialização e ciclos intensos de criação e descarte de objetos. Em uma API limitada por banco de dados ou rede, o alocador pode representar uma fração irrelevante do tempo total. Por isso, comece com [profiling de CPU e memória em produção](/blog/rust-profiling-performance-producao-2026/) e use um [flame graph em Rust](/blog/cargo-flamegraph-rust-profiling-performance-2026/) antes de adicionar outra variável ao sistema.

## Resposta rápida: qual alocador testar?

| Cenário | Primeiro experimento | Motivo |
|---|---|---|
| Serviço concorrente com alta taxa de alocação | padrão vs jemalloc vs mimalloc | o padrão de threads pode mudar contenção e fragmentação |
| CLI curta, executada por poucos segundos | mantenha o padrão primeiro | inicialização e simplicidade pesam mais |
| API limitada por Postgres ou chamadas externas | profile antes de trocar | o alocador provavelmente não é o gargalo principal |
| Processo longo com RSS alto após picos | compare memória ao longo do tempo | uma foto isolada não mostra retenção e devolução ao SO |
| Binário para vários targets | valide a matriz completa | dependências nativas podem mudar portabilidade e empacotamento |
| Embedded ou `no_std` | não aplique este guia diretamente | o modelo de memória e a disponibilidade de heap são diferentes |

A recomendação prática é simples: **trate o alocador como uma implementação substituível, não como uma otimização obrigatória de Rust**.

## O que o alocador global faz

Coleções e tipos como `Vec`, `String`, `Box`, `HashMap` e `Arc` frequentemente precisam reservar memória no heap. O alocador global atende pedidos de alocação, realocação e desalocação feitos por essas estruturas. O Rust garante regras de ownership e segurança na interface segura, mas a estratégia usada para organizar blocos, arenas, caches por thread e retorno de páginas ao sistema pertence ao alocador.

Na ausência de `#[global_allocator]`, o programa usa o alocador de sistema escolhido para o target. Isso significa que a baseline pode variar entre Linux, Windows e macOS. Compare candidatos no mesmo sistema operacional, imagem, arquitetura e configuração de produção; um resultado obtido no notebook não autoriza uma conclusão sobre o container Linux do serviço.

Também vale separar dois conceitos:

- **vazamento lógico:** o programa ainda mantém referências para dados que deveriam ter sido descartados;
- **RSS alto após desalocação:** o programa liberou objetos, mas o alocador ou o sistema operacional ainda mantém páginas mapeadas para reutilização.

Trocar de alocador pode mudar o segundo comportamento. Não corrige o primeiro. Se um cache cresce sem limite, nenhum allocator substitui uma política de expiração.

## jemalloc e mimalloc: diferenças úteis

O **jemalloc** é um alocador maduro, conhecido pelo uso de arenas e estruturas voltadas a concorrência, fragmentação e observabilidade. No ecossistema Rust, uma integração comum é a crate `tikv-jemallocator`. Ele aparece com frequência em serviços long-lived e workloads com muitas threads, mas isso não significa que sempre consumirá menos memória ou entregará menor latência.

O **mimalloc**, desenvolvido originalmente pela Microsoft, também usa técnicas como páginas, listas locais e caminhos rápidos por thread. A crate `mimalloc` oferece uma integração direta com o atributo de alocador global. Em alguns workloads ele se destaca por throughput e ergonomia de adoção; em outros, a diferença para o sistema fica dentro do ruído.

| Critério | jemalloc | mimalloc | Alocador padrão |
|---|---|---|---|
| Integração Rust comum | `tikv-jemallocator` | `mimalloc` | nenhuma dependência extra |
| Perfil típico de teste | serviços concorrentes e long-lived | serviços, ferramentas e cargas multithread | qualquer projeto como baseline |
| Dependência nativa | sim | sim | fornecida pelo target/sistema |
| Observabilidade específica | possui opções e ferramentas próprias | possui opções de diagnóstico próprias | depende da plataforma |
| Melhor escolha automática | não | não | é o ponto de partida |

Não use essa tabela como ranking. Ela serve para escolher candidatos de benchmark.

## Configurando jemalloc em Rust

Adicione a dependência ao `Cargo.toml`:

```toml
[dependencies]
tikv-jemallocator = "0.6"
```

Na raiz do binário, normalmente em `src/main.rs`, declare o alocador:

```rust
use tikv_jemallocator::Jemalloc;

#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

fn main() {
    let dados: Vec<String> = (0..100_000)
        .map(|n| format!("registro-{n}"))
        .collect();

    println!("{} registros", dados.len());
}
```

Consulte a release atual da crate antes de fixar a versão real do projeto. Use o lockfile e valide todos os targets suportados. Se você mantém biblioteca, não imponha um alocador global ao consumidor: essa decisão normalmente pertence ao binário final, que conhece o ambiente e a carga.

## Configurando mimalloc em Rust

A integração é parecida:

```toml
[dependencies]
mimalloc = "0.1"
```

```rust
use mimalloc::MiMalloc;

#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

fn main() {
    let mut buffers = Vec::with_capacity(50_000);

    for n in 0..50_000 {
        buffers.push(vec![n as u8; 256]);
    }

    println!("{} buffers", buffers.len());
}
```

As versões acima ilustram a configuração. Confirme versões, features e requisitos de build na documentação oficial e no `cargo tree` do projeto. A adoção operacional inclui atualizar imagens, SBOM, política de licenças e auditoria da cadeia de dependências — temas aprofundados no guia de [cargo-audit e RustSec](/blog/cargo-audit-vulnerabilidades-dependencias-rust-ci-2026/).

## Como manter três builds comparáveis

Editar `main.rs` antes de cada teste funciona, mas dificulta reproduzir o experimento. Uma alternativa é usar features mutuamente exclusivas:

```toml
[features]
default = []
alloc-jemalloc = ["dep:tikv-jemallocator"]
alloc-mimalloc = ["dep:mimalloc"]

[dependencies]
tikv-jemallocator = { version = "0.6", optional = true }
mimalloc = { version = "0.1", optional = true }
```

```rust
#[cfg(all(feature = "alloc-jemalloc", feature = "alloc-mimalloc"))]
compile_error!("ative apenas um alocador global");

#[cfg(feature = "alloc-jemalloc")]
#[global_allocator]
static GLOBAL_JEMALLOC: tikv_jemallocator::Jemalloc =
    tikv_jemallocator::Jemalloc;

#[cfg(feature = "alloc-mimalloc")]
#[global_allocator]
static GLOBAL_MIMALLOC: mimalloc::MiMalloc =
    mimalloc::MiMalloc;
```

Agora gere os candidatos:

```bash
cargo build --release
cargo build --release --features alloc-jemalloc
cargo build --release --features alloc-mimalloc
```

Não compare arquivos sobrescritos no mesmo `target/release`. Copie cada binário para um nome identificável ou use diretórios de target separados:

```bash
CARGO_TARGET_DIR=target/system cargo build --release
CARGO_TARGET_DIR=target/jemalloc cargo build --release --features alloc-jemalloc
CARGO_TARGET_DIR=target/mimalloc cargo build --release --features alloc-mimalloc
```

Isso evita executar o candidato errado e deixa o experimento auditável na [CI/CD do projeto Rust](/artigos/ci-cd-rust/).

## Benchmark: o que medir de verdade

Microbenchmarks com [Criterion](/ecossistema/criterion/) ajudam quando você isolou uma função que aloca muito. Para escolher o alocador de um serviço, porém, prefira também um teste de carga do processo completo.

Meça pelo menos:

1. **throughput:** requests, jobs ou mensagens por segundo;
2. **latência:** p50, p95 e p99, não apenas média;
3. **CPU:** total e por core durante a fase estável;
4. **RSS:** baseline, pico e valor depois da carga;
5. **tempo de recuperação:** quanto o processo demora para estabilizar após uma rajada;
6. **falhas e timeouts:** performance sem correção não é ganho;
7. **tamanho do binário e imagem:** importante para deploy e cold start;
8. **tempo de build:** dependências nativas também têm custo operacional.

Um roteiro A/B confiável:

```text
1. fixe commit, Cargo.lock, toolchain e profile de release;
2. use a mesma máquina ou runner dedicado;
3. aqueça o serviço e o gerador de carga;
4. execute cada candidato várias vezes em ordem alternada;
5. descarte warm-up e registre dispersão;
6. repita com carga normal e com pico realista;
7. só escolha quando o resultado for consistente.
```

Se “mimalloc venceu” por 1,5%, mas a variação entre rodadas é 4%, você mediu ruído. Se jemalloc melhora throughput, mas aumenta p99 ou mantém RSS alto por mais tempo, a decisão depende do SLO e do limite de memória do serviço.

## Exemplo de workload de alocação

O programa abaixo não substitui a carga real, mas ajuda a verificar se os três builds estão funcionando:

```rust
use std::collections::HashMap;
use std::hint::black_box;
use std::time::Instant;

fn rodada() -> usize {
    let mut mapa = HashMap::with_capacity(200_000);

    for n in 0..200_000 {
        mapa.insert(
            format!("cliente-{n}"),
            vec![(n % 251) as u8; 128 + (n % 512)],
        );
    }

    for n in (0..200_000).step_by(3) {
        mapa.remove(&format!("cliente-{n}"));
    }

    mapa.values().map(Vec::len).sum()
}

fn main() {
    for _ in 0..3 {
        let inicio = Instant::now();
        let total = black_box(rodada());
        println!("bytes={total} tempo={:?}", inicio.elapsed());
    }
}
```

O exemplo mistura tamanhos e desalocações, mas continua artificial. Um benchmark útil deve reproduzir o comportamento do produto: payloads, concorrência, filas, cache, serialização e duração semelhantes aos de produção.

## Armadilhas comuns

### Trocar o alocador antes de remover alocações

Se o flame graph aponta `format!`, clones, `collect()` intermediários e buffers que crescem repetidamente, primeiro reduza o trabalho. `Vec::with_capacity`, reutilização de buffers, empréstimos e estruturas adequadas podem eliminar alocações inteiras. O artigo de [otimização de performance em Rust](/artigos/otimizacao-performance/) mostra esse caminho.

### Medir apenas tempo total

Dois candidatos podem terminar o teste no mesmo tempo, mas apresentar p99, CPU e RSS diferentes. Serviços têm SLOs e limites de container; uma única métrica esconde trade-offs.

### Confundir memória reservada com vazamento

RSS alto não prova que objetos continuam alcançáveis. Use profiler de heap, métricas do processo e observação depois de rajadas. Confirme se a aplicação retém dados ou se o allocator mantém páginas disponíveis para reutilização.

### Ignorar targets e imagens

Uma dependência nativa pode funcionar no Linux glibc do notebook e falhar na imagem final, em outro target ou no cross-compilation. Valide o mesmo Dockerfile usado em produção; o guia de [builds Rust otimizados com Docker](/blog/rust-docker-builds-otimizados-producao-2026/) ajuda a manter essa comparação honesta.

### Ativar dois alocadores

Só pode existir um alocador global no binário. Proteja features incompatíveis com `compile_error!` e teste a matriz. Em workspaces, mantenha a escolha no crate binário, não espalhada entre bibliotecas.

## Quando a troca vale a pena

Considere adotar jemalloc ou mimalloc quando:

- profiling mostra custo relevante em alocação, desalocação ou contenção;
- o serviço é long-lived e multithread;
- o benchmark representa a carga real;
- o ganho se repete em várias rodadas;
- p99, CPU e memória caminham na direção dos SLOs;
- a matriz de build e deploy continua suportada;
- existe rollback simples para o alocador padrão.

Mantenha o padrão quando o benefício é marginal, instável ou restrito a um microbenchmark que não representa o produto. Menos dependências e menos configuração também são ganhos de engenharia.

## Alocadores como tema de carreira Rust

Saber configurar `#[global_allocator]` é fácil. O diferencial em uma [entrevista de backend Rust](/carreira/entrevista-rust-backend/) é explicar **como você provaria que a troca vale a pena**. Uma boa resposta menciona baseline, flame graph, carga representativa, percentis, RSS, concorrência, targets, rollback e decisão orientada por SLO.

Para portfólio, publique um repositório pequeno com:

- três builds reproduzíveis;
- script de carga;
- tabela com várias rodadas;
- gráficos de latência e memória;
- conclusão que aceite “nenhuma troca” como resultado válido;
- README explicando limitações do experimento.

Esse projeto demonstra sistemas, método científico e operação — competências úteis para [vagas Rust](/vagas/), mesmo quando a empresa não usa jemalloc nem mimalloc.

## Checklist antes do deploy

- [ ] O profiler mostrou que alocação merece atenção?
- [ ] O alocador padrão entrou na comparação?
- [ ] Commit, toolchain, lockfile e profile são idênticos?
- [ ] A carga representa concorrência e payloads reais?
- [ ] Foram medidos p95/p99, CPU, RSS e recuperação após pico?
- [ ] O resultado supera o ruído entre rodadas?
- [ ] Todos os targets e a imagem final compilam?
- [ ] Licença, SBOM e auditoria de dependências foram atualizados?
- [ ] Existe feature ou commit simples para rollback?
- [ ] A decisão e os dados ficaram registrados?

## Conclusão

`jemalloc` e `mimalloc` são candidatos legítimos para workloads Rust intensivos em alocação, mas nenhum deles é um upgrade automático. Configure cada opção no binário, preserve o alocador de sistema como baseline e faça um experimento controlado. Meça o que o usuário e a operação sentem: throughput, cauda de latência, CPU, RSS e estabilidade após picos.

Se o ganho for claro e repetível, leve a mudança para um canário, observe métricas e mantenha rollback. Se não for, a melhor otimização é não adicionar complexidade. Em Rust, performance profissional não é escolher a biblioteca com o benchmark mais bonito; é demonstrar, no seu workload, por que uma mudança melhora o sistema inteiro.

## Perguntas frequentes

### Como usar jemalloc como alocador global em Rust?

Adicione `tikv-jemallocator`, declare uma `static` do tipo `Jemalloc` com `#[global_allocator]` no crate binário e compile todos os targets suportados. Depois compare o resultado ao alocador padrão com carga representativa.

### Como usar mimalloc em Rust?

Adicione a crate `mimalloc` e declare `mimalloc::MiMalloc` como alocador global. A alteração de código é pequena; o trabalho importante é medir latência, throughput, CPU e memória com rigor.

### Qual é mais rápido: jemalloc ou mimalloc?

Depende do workload, da concorrência, dos tamanhos de alocação e da plataforma. Não existe vencedor universal. Inclua também o alocador padrão na disputa.

### Trocar o alocador reduz o RSS?

Pode reduzir em uma carga e aumentar em outra. Observe o processo durante estado estável, picos e recuperação, e diferencie retenção lógica de memória mantida pelo allocator para reutilização.

### Uma biblioteca Rust deve definir o alocador global?

Normalmente não. A biblioteca não conhece todas as restrições do programa final. Deixe a escolha para o binário ou aplicação que controla deploy, métricas e compatibilidade de targets.
