Arena Pattern em Zig
O Arena Pattern agrupa múltiplas alocações em uma única região de memória que é liberada toda de uma vez. Em vez de rastrear e liberar cada alocação individualmente, você aloca livremente durante uma fase de trabalho e libera tudo no final. Em Zig, std.heap.ArenaAllocator implementa esse padrão nativamente.
Quando Usar
- Processamento de requisições (alocar para processar, liberar ao finalizar)
- Parsing e compilação (alocar AST, liberar após gerar código)
- Operações batch com tempo de vida definido
- Game frames (alocar entidades temporárias por frame)
- Qualquer operação com fase de alocação seguida de liberação total
Implementação com ArenaAllocator
const std = @import("std");
pub fn main() !void {
// Allocator pai (quem realmente gerencia a memória)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Arena: aloca rápido, libera tudo junto
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); // libera TUDO de uma vez
const alloc = arena.allocator();
// Alocar livremente — sem precisar de free individual!
const nome = try alloc.dupe(u8, "Zig Brasil");
const numeros = try alloc.alloc(i32, 100);
const buffer = try alloc.alloc(u8, 4096);
// Usar os dados...
_ = nome;
for (numeros, 0..) |*n, i| n.* = @intCast(i);
@memset(buffer, 0);
// Ao final: arena.deinit() libera TUDO
// Não precisa de defer alloc.free() para cada alocação!
}
Arena por Requisição
const std = @import("std");
const Requisicao = struct {
metodo: []const u8,
path: []const u8,
corpo: []const u8,
};
const Resposta = struct {
status: u16,
corpo: []const u8,
};
fn processarRequisicao(
arena_alloc: std.mem.Allocator,
req: Requisicao,
) !Resposta {
// Todas alocações temporárias usam a arena
const path_normalizado = try std.fmt.allocPrint(arena_alloc, "/{s}", .{
std.mem.trimLeft(u8, req.path, "/"),
});
var partes = std.ArrayList([]const u8).init(arena_alloc);
var iter = std.mem.splitSequence(u8, path_normalizado, "/");
while (iter.next()) |parte| {
try partes.append(parte);
}
const corpo_resposta = try std.fmt.allocPrint(arena_alloc, "OK: {s} {s}", .{
req.metodo, path_normalizado,
});
return .{
.status = 200,
.corpo = corpo_resposta,
};
// Nenhum free necessário — arena cuida de tudo
}
fn servidorLoop(parent_alloc: std.mem.Allocator) !void {
// Simular processamento de requisições
for (0..1000) |_| {
// Nova arena para cada requisição
var arena = std.heap.ArenaAllocator.init(parent_alloc);
defer arena.deinit(); // limpa tudo ao final da requisição
const req = Requisicao{
.metodo = "GET",
.path = "/api/dados",
.corpo = "",
};
const resp = try processarRequisicao(arena.allocator(), req);
_ = resp;
// arena.deinit() limpa tudo — zero leaks, zero fragmentação
}
}
Arena com Reset (Reutilização)
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
// Processar múltiplos batches reutilizando memória
for (0..100) |batch| {
const alloc = arena.allocator();
// Alocar para este batch
const dados = try alloc.alloc(u8, 1024);
_ = dados;
const resultado = try std.fmt.allocPrint(alloc, "Batch {d} processado", .{batch});
_ = resultado;
// Resetar arena — libera tudo mas MANTÉM a memória alocada do SO
_ = arena.reset(.retain_capacity);
// Próximo batch reutiliza a mesma memória — sem novas syscalls
}
}
Arena sobre Buffer Estático
const std = @import("std");
pub fn main() !void {
// Arena sobre buffer da stack — zero heap allocations!
var buffer: [64 * 1024]u8 = undefined; // 64KB na stack
var fba = std.heap.FixedBufferAllocator.init(&buffer);
var arena = std.heap.ArenaAllocator.init(fba.allocator());
defer arena.deinit();
const alloc = arena.allocator();
// Alocações vêm do buffer na stack
const dados = try alloc.alloc(u8, 1024);
_ = dados;
}
Comparação: Sem Arena vs Com Arena
// SEM arena — muitos defers, fácil esquecer um
fn semArena(allocator: std.mem.Allocator) !void {
const a = try allocator.alloc(u8, 100);
defer allocator.free(a);
const b = try allocator.alloc(u8, 200);
defer allocator.free(b);
const c = try allocator.alloc(u8, 300);
defer allocator.free(c);
// Cada alocação precisa de seu próprio free
}
// COM arena — um único defer limpa tudo
fn comArena(parent: std.mem.Allocator) !void {
var arena = std.heap.ArenaAllocator.init(parent);
defer arena.deinit(); // UM defer para tudo
const alloc = arena.allocator();
const a = try alloc.alloc(u8, 100);
const b = try alloc.alloc(u8, 200);
const c = try alloc.alloc(u8, 300);
_ = a; _ = b; _ = c;
// Simples, limpo, sem risco de leak
}
Quando Evitar
- Alocações que precisam sobreviver além do escopo da arena
- Quando objetos têm tempos de vida muito diferentes
- Quando é preciso liberar objetos individualmente (use GPA)
- Objetos enormes que desperdiçariam memória se mantidos até o final
Veja Também
- Allocators — Todos os tipos de alocadores
- Pool de Objetos — Reutilização individual
- Flyweight — Compartilhar estado para economizar memória
- FAQ Memória — Perguntas sobre gerenciamento de memória
- Troubleshooting: Memory Leak — Encontrar vazamentos