Otimizacao sem profiling e adivinhacao. Profiling mostra exatamente onde seu programa gasta tempo, quanta memoria usa e onde estao os gargalos. Neste artigo, exploramos as principais ferramentas de profiling para codigo Zig.
Continuacao do artigo sobre SIMD e vetorizacao em Zig.
A Regra de Ouro: Meça Antes de Otimizar
Donald Knuth disse: “Otimizacao prematura e a raiz de todo mal.” A versao moderna dessa frase e:
- Faca funcionar — codigo correto primeiro
- Meça — profile para encontrar o gargalo real
- Otimize o hotspot — foque nos 20% que causam 80% do tempo
- Meça de novo — confirme que a otimizacao funcionou
Linux perf
perf e a ferramenta de profiling mais poderosa no Linux. Ela usa hardware performance counters da CPU para medir com precisao e baixo overhead.
Compilando para Profiling
# Compilar com simbolos de debug + otimizacoes
zig build -Doptimize=ReleaseSafe
# ReleaseSafe mantem simbolos de debug e bounds checking
# mas aplica otimizacoes do compilador
Profiling Basico com perf
# Gravar perfil de execucao
perf record -g ./zig-out/bin/minha-app
# Visualizar resultados
perf report
# Estatisticas rapidas (sem gravacao)
perf stat ./zig-out/bin/minha-app
Saida tipica do perf stat:
Performance counter stats for './zig-out/bin/minha-app':
1,234.56 msec task-clock
42 context-switches
3 cpu-migrations
12,345 page-faults
4,567,890,123 cycles
8,901,234,567 instructions # 1.95 insn per cycle
1,234,567,890 branches
12,345,678 branch-misses # 1.00% of all branches
345,678,901 L1-dcache-loads
23,456,789 L1-dcache-load-misses # 6.79% of all L1-dcache
Analisando Cache Misses
# Medir cache misses especificamente
perf stat -e L1-dcache-loads,L1-dcache-load-misses,LLC-loads,LLC-load-misses \
./zig-out/bin/minha-app
# Encontrar funcoes com mais cache misses
perf record -e cache-misses -g ./zig-out/bin/minha-app
perf report --sort=dso,symbol
Flame Graphs
Flame graphs mostram visualmente onde seu programa gasta tempo:
# Gravar dados
perf record -F 99 -g ./zig-out/bin/minha-app
# Gerar flame graph (requer FlameGraph tools)
perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg
# Abrir no navegador
xdg-open flamegraph.svg
Tracy Profiler
Tracy e um profiler em tempo real, ideal para games e aplicacoes interativas. O Zig tem integracao nativa via std.debug.Trace.
Integrando Tracy no Codigo Zig
const std = @import("std");
const tracy = @import("tracy");
pub fn main() void {
// Zona de profiling com nome
{
const zone = tracy.trace(@src());
defer zone.end();
// Codigo a ser medido
processarDados();
}
// Zona com cor customizada
{
const zone = tracy.trace(@src());
defer zone.end();
zone.setColor(0xFF0000); // Vermelho
zone.setName("Renderizar Frame");
renderizar();
}
}
fn processarDados() void {
const zone = tracy.trace(@src());
defer zone.end();
// Marcar alocacoes de memoria
const allocator = tracy.tracyAllocator(std.heap.page_allocator);
const dados = allocator.alloc(u8, 1024) catch return;
defer allocator.free(dados);
// ... processar ...
}
fn renderizar() void {
const zone = tracy.trace(@src());
defer zone.end();
// Plotar valor no timeline do Tracy
tracy.plot("FPS", 60.0);
tracy.plot("Memoria MB", 128.5);
}
Configurando Tracy no build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Flag para habilitar/desabilitar Tracy
const enable_tracy = b.option(bool, "tracy", "Habilitar Tracy profiler") orelse false;
const exe = b.addExecutable(.{
.name = "minha-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
if (enable_tracy) {
const tracy_dep = b.dependency("tracy", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("tracy", tracy_dep.module("tracy"));
exe.linkLibC();
}
b.installArtifact(exe);
}
Valgrind
Valgrind e essencial para encontrar problemas de memoria e analisar comportamento de cache.
Callgrind: Profiling de CPU
# Executar com Callgrind
valgrind --tool=callgrind ./zig-out/bin/minha-app
# Visualizar com KCachegrind
kcachegrind callgrind.out.*
Cachegrind: Analise de Cache
# Simular comportamento de cache
valgrind --tool=cachegrind ./zig-out/bin/minha-app
# Saida mostra misses por funcao:
# ==1234== D refs: 12,345,678
# ==1234== D1 misses: 234,567 (1.90%)
# ==1234== LLd misses: 12,345 (0.10%)
# Anotar codigo fonte com contagens de cache
cg_annotate cachegrind.out.* src/main.zig
Massif: Profiling de Memoria
# Rastrear uso de memoria ao longo do tempo
valgrind --tool=massif ./zig-out/bin/minha-app
# Visualizar
ms_print massif.out.*
Instrumentacao Manual em Zig
Timer Embutido para Profiling Leve
const std = @import("std");
/// Profiler leve embutido no codigo
const Profiler = struct {
const MAX_ZONAS = 64;
const Zona = struct {
nome: []const u8,
total_ns: u64 = 0,
chamadas: u64 = 0,
min_ns: u64 = std.math.maxInt(u64),
max_ns: u64 = 0,
};
zonas: [MAX_ZONAS]Zona = undefined,
num_zonas: usize = 0,
pub fn registrarZona(self: *Profiler, nome: []const u8) usize {
const id = self.num_zonas;
self.zonas[id] = .{ .nome = nome };
self.num_zonas += 1;
return id;
}
pub fn iniciarMedicao(_: *Profiler) std.time.Timer {
return std.time.Timer.start() catch unreachable;
}
pub fn finalizarMedicao(self: *Profiler, id: usize, timer: std.time.Timer) void {
const elapsed = timer.read();
self.zonas[id].total_ns += elapsed;
self.zonas[id].chamadas += 1;
self.zonas[id].min_ns = @min(self.zonas[id].min_ns, elapsed);
self.zonas[id].max_ns = @max(self.zonas[id].max_ns, elapsed);
}
pub fn relatorio(self: *Profiler) void {
std.debug.print("\n=== Relatorio de Performance ===\n", .{});
std.debug.print("{s:<30} {s:>10} {s:>10} {s:>10} {s:>10} {s:>10}\n", .{
"Zona", "Chamadas", "Total(ms)", "Media(us)", "Min(us)", "Max(us)",
});
std.debug.print("{s:-<90}\n", .{""});
for (self.zonas[0..self.num_zonas]) |zona| {
const total_ms = zona.total_ns / std.time.ns_per_ms;
const media_us = if (zona.chamadas > 0)
zona.total_ns / zona.chamadas / std.time.ns_per_us
else
0;
const min_us = zona.min_ns / std.time.ns_per_us;
const max_us = zona.max_ns / std.time.ns_per_us;
std.debug.print("{s:<30} {d:>10} {d:>10} {d:>10} {d:>10} {d:>10}\n", .{
zona.nome, zona.chamadas, total_ms, media_us, min_us, max_us,
});
}
}
};
var profiler = Profiler{};
// Uso global
const ZONA_PROCESSAR = blk: {
break :blk 0;
};
fn processarComProfile(dados: []const u8) void {
const timer = profiler.iniciarMedicao();
defer profiler.finalizarMedicao(0, timer);
// ... codigo real ...
_ = dados;
}
Identificando Hotspots Comuns
1. Alocacoes Excessivas
// LENTO: aloca e desaloca em cada iteracao
fn processarLento(items: []const Item, allocator: std.mem.Allocator) !void {
for (items) |item| {
const resultado = try allocator.alloc(u8, item.tamanho);
defer allocator.free(resultado);
// ... processar ...
}
}
// RAPIDO: reutilizar buffer
fn processarRapido(items: []const Item, allocator: std.mem.Allocator) !void {
var buffer = std.ArrayList(u8).init(allocator);
defer buffer.deinit();
for (items) |item| {
buffer.clearRetainingCapacity();
try buffer.resize(item.tamanho);
// ... processar usando buffer.items ...
}
}
2. Copias Desnecessarias
// LENTO: copia a struct inteira
fn processarStruct(s: MinhaStructGrande) void {
// s e uma copia — pode ser 256+ bytes
_ = s;
}
// RAPIDO: passa por referencia
fn processarStructRef(s: *const MinhaStructGrande) void {
// s e um ponteiro — 8 bytes
_ = s;
}
3. Branch Mispredictions
// LENTO: branch imprevisivel
fn filtrarSlow(dados: []const u32) u64 {
var soma: u64 = 0;
for (dados) |v| {
if (v > 128) { // Branch imprevisivel para dados aleatorios
soma += v;
}
}
return soma;
}
// RAPIDO: branchless com bitmask
fn filtrarFast(dados: []const u32) u64 {
var soma: u64 = 0;
for (dados) |v| {
// Sem branch: mascara e 0 ou 0xFFFFFFFF
const mask: u32 = if (v > 128) 0xFFFFFFFF else 0;
soma += v & mask;
}
return soma;
}
Conclusao
Profiling nao e opcional — e o unico caminho confiavel para otimizacao eficaz. Use perf para analise de CPU e cache no Linux, Tracy para profiling em tempo real de aplicacoes interativas, e Valgrind para analise profunda de memoria. Combine essas ferramentas com benchmarking robusto e voce tera uma visao completa de onde seu codigo Zig pode melhorar.
Proximo Artigo
No Artigo 5: Otimizacao Real, aplicamos todas as tecnicas desta serie em um estudo de caso real, com medicoes antes e depois.
Conteudo Relacionado
- SIMD e Vetorizacao — Artigo anterior
- Otimizacao Real — Proximo artigo
- Zig Debugging — Depuracao
- Profiling e Benchmarks — Tutorial basico
- Ferramentas de Desenvolvimento — DevTools