Pool de Objetos em Zig
O padrão Pool de Objetos mantém uma coleção de objetos pré-alocados e reutilizáveis, evitando o custo de alocação e desalocação frequente. Em Zig, com controle explícito de memória, este padrão é especialmente útil para aplicações de alta performance, servidores e sistemas embarcados.
Quando Usar
- Servidores que criam/destroem conexões frequentemente
- Game loops com criação/destruição de entidades
- Pools de buffers para I/O
- Sistemas embarcados com memória limitada
- Evitar fragmentação de memória
Implementação Básica
const std = @import("std");
fn Pool(comptime T: type, comptime tamanho: usize) type {
return struct {
const Self = @This();
itens: [tamanho]T = undefined,
disponiveis: [tamanho]bool = [_]bool{true} ** tamanho,
inicializador: *const fn () T,
pub fn init(inicializador: *const fn () T) Self {
var self = Self{
.inicializador = inicializador,
};
for (&self.itens) |*item| {
item.* = inicializador();
}
return self;
}
pub fn adquirir(self: *Self) ?*T {
for (self.disponiveis, 0..) |*disp, i| {
if (disp.*) {
disp.* = false;
return &self.itens[i];
}
}
return null; // pool esgotado
}
pub fn liberar(self: *Self, ptr: *T) void {
const inicio = @intFromPtr(&self.itens[0]);
const endereco = @intFromPtr(ptr);
const indice = (endereco - inicio) / @sizeOf(T);
if (indice < tamanho) {
self.itens[indice] = (self.inicializador)(); // resetar
self.disponiveis[indice] = true;
}
}
pub fn disponiveisCount(self: *const Self) usize {
var count: usize = 0;
for (self.disponiveis) |d| {
if (d) count += 1;
}
return count;
}
};
}
const Buffer = struct {
dados: [1024]u8 = undefined,
tamanho: usize = 0,
fn criar() Buffer {
return Buffer{};
}
};
pub fn main() void {
var pool = Pool(Buffer, 16).init(Buffer.criar);
// Adquirir buffer do pool
if (pool.adquirir()) |buf| {
buf.dados[0] = 'A';
buf.tamanho = 1;
std.debug.print("Buffer adquirido, disponíveis: {d}\n", .{pool.disponiveisCount()});
// Devolver ao pool quando terminar
pool.liberar(buf);
std.debug.print("Buffer liberado, disponíveis: {d}\n", .{pool.disponiveisCount()});
}
}
Pool com Allocator Dinâmico
const std = @import("std");
fn PoolDinamico(comptime T: type) type {
return struct {
const Self = @This();
livres: std.ArrayList(*T),
allocator: std.mem.Allocator,
total_criados: usize = 0,
max_tamanho: usize,
pub fn init(allocator: std.mem.Allocator, max: usize) Self {
return .{
.livres = std.ArrayList(*T).init(allocator),
.allocator = allocator,
.max_tamanho = max,
};
}
pub fn deinit(self: *Self) void {
for (self.livres.items) |item| {
self.allocator.destroy(item);
}
self.livres.deinit();
}
pub fn adquirir(self: *Self) !*T {
if (self.livres.popOrNull()) |item| {
return item;
}
// Criar novo se abaixo do limite
if (self.total_criados < self.max_tamanho) {
const novo = try self.allocator.create(T);
novo.* = std.mem.zeroes(T);
self.total_criados += 1;
return novo;
}
return error.PoolEsgotado;
}
pub fn liberar(self: *Self, item: *T) !void {
item.* = std.mem.zeroes(T); // resetar
try self.livres.append(item);
}
};
}
Quando Evitar
- Objetos pequenos e baratos de criar (overhead do pool supera o benefício)
- Quando o tamanho do pool é difícil de prever
- Objetos com estado complexo difícil de resetar
- Aplicações onde memória não é gargalo
Veja Também
- Arena Pattern — Alocação em bloco sem reutilização individual
- Allocators — Alocadores de memória em Zig
- Flyweight — Compartilhar estado entre objetos
- Concorrência — Pool thread-safe
- FAQ Performance — Otimização de memória