---
title: "Otimização Real: Estudo de Caso em Zig — De 200ms para 12ms"
url: "https://ziglang.com.br/tutoriais/zig-real-world-optimization/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-real-world-optimization.MD"
description: "Estudo de caso completo de otimizacao de performance em Zig. Profiling, cache optimization, SIMD e benchmarking com resultados antes e depois."
date: "2026-02-21"
author: ""
---

# Otimização Real: Estudo de Caso em Zig — De 200ms para 12ms

Estudo de caso completo de otimizacao de performance em Zig. Profiling, cache optimization, SIMD e benchmarking com resultados antes e depois.


Teoria sem pratica nao otimiza nada. Neste artigo final da serie, vamos otimizar um projeto real passo a passo — um processador de logs que analisa milhoes de linhas — aplicando todas as tecnicas que aprendemos: benchmarking, cache-friendly layout, SIMD e profiling. Cada etapa inclui medicoes antes e depois.

> Continuacao do artigo sobre [ferramentas de profiling](/tutoriais/zig-performance/artigo-4-profiling-tools/).

## O Projeto: Analisador de Logs

Nosso projeto e um analisador de logs de acesso HTTP que precisa:

1. Ler linhas de log do formato: `IP METODO URL STATUS TEMPO_MS`
2. Contar requisicoes por status code (200, 404, 500, etc.)
3. Calcular tempo medio de resposta
4. Encontrar os IPs com mais requisicoes

Meta: processar **10 milhoes de linhas** o mais rapido possivel.

## Versao Inicial: Ingênua

```zig
const std = @import("std");

const LogEntry = struct {
    ip: []const u8,
    metodo: []const u8,
    url: []const u8,
    status: u16,
    tempo_ms: f64,
};

const Estatisticas = struct {
    total_requests: u64 = 0,
    soma_tempo: f64 = 0,
    contagem_status: std.AutoHashMap(u16, u64),
    contagem_ips: std.StringHashMap(u64),
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator) Estatisticas {
        return .{
            .contagem_status = std.AutoHashMap(u16, u64).init(allocator),
            .contagem_ips = std.StringHashMap(u64).init(allocator),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *Estatisticas) void {
        self.contagem_status.deinit();
        // Liberar chaves alocadas
        var it = self.contagem_ips.iterator();
        while (it.next()) |entry| {
            self.allocator.free(entry.key_ptr.*);
        }
        self.contagem_ips.deinit();
    }

    pub fn registrar(self: *Estatisticas, entry: LogEntry) !void {
        self.total_requests += 1;
        self.soma_tempo += entry.tempo_ms;

        // Contagem por status
        const status_count = try self.contagem_status.getOrPut(entry.status);
        if (!status_count.found_existing) status_count.value_ptr.* = 0;
        status_count.value_ptr.* += 1;

        // Contagem por IP
        const ip_count = try self.contagem_ips.getOrPut(
            try self.allocator.dupe(u8, entry.ip),
        );
        if (!ip_count.found_existing) ip_count.value_ptr.* = 0;
        ip_count.value_ptr.* += 1;
    }

    pub fn tempoMedio(self: Estatisticas) f64 {
        if (self.total_requests == 0) return 0;
        return self.soma_tempo / @as(f64, @floatFromInt(self.total_requests));
    }
};

/// Versao V1: implementacao ingenua
fn parseLine(linha: []const u8) !LogEntry {
    var partes = std.mem.splitScalar(u8, linha, ' ');

    const ip = partes.next() orelse return error.FormatoInvalido;
    const metodo = partes.next() orelse return error.FormatoInvalido;
    const url = partes.next() orelse return error.FormatoInvalido;
    const status_str = partes.next() orelse return error.FormatoInvalido;
    const tempo_str = partes.next() orelse return error.FormatoInvalido;

    return .{
        .ip = ip,
        .metodo = metodo,
        .url = url,
        .status = try std.fmt.parseInt(u16, status_str, 10),
        .tempo_ms = try std.fmt.parseFloat(f64, tempo_str),
    };
}

fn analisarV1(dados: []const u8, allocator: std.mem.Allocator) !Estatisticas {
    var stats = Estatisticas.init(allocator);

    var linhas = std.mem.splitScalar(u8, dados, '\n');
    while (linhas.next()) |linha| {
        if (linha.len == 0) continue;
        const entry = parseLine(linha) catch continue;
        try stats.registrar(entry);
    }

    return stats;
}
```

### Benchmark V1

```
V1 (Ingênua): 10M linhas em 4.200 ms
  - Parse: 1.800 ms
  - Estatísticas: 2.400 ms
  - Memória: 890 MB
```

## Otimizacao 1: Reduzir Alocacoes

O maior gargalo e a alocacao de strings para cada IP. Vamos usar um string intern pool:

```zig
const StringPool = struct {
    strings: std.StringHashMap(void),
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator) StringPool {
        return .{
            .strings = std.StringHashMap(void).init(allocator),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *StringPool) void {
        var it = self.strings.keyIterator();
        while (it.next()) |key| {
            self.allocator.free(key.*);
        }
        self.strings.deinit();
    }

    /// Retorna ponteiro estavel para string interned
    pub fn intern(self: *StringPool, str: []const u8) ![]const u8 {
        if (self.strings.getKey(str)) |existing| {
            return existing; // Ja existe, reutilizar
        }
        const owned = try self.allocator.dupe(u8, str);
        try self.strings.put(owned, {});
        return owned;
    }
};

/// V2: com string interning
fn analisarV2(dados: []const u8, allocator: std.mem.Allocator) !Estatisticas {
    var pool = StringPool.init(allocator);
    defer pool.deinit();

    var stats = Estatisticas.init(allocator);

    var linhas = std.mem.splitScalar(u8, dados, '\n');
    while (linhas.next()) |linha| {
        if (linha.len == 0) continue;
        var entry = parseLine(linha) catch continue;
        entry.ip = try pool.intern(entry.ip);
        try stats.registrar(entry);
    }

    return stats;
}
```

```
V2 (String interning): 10M linhas em 2.800 ms (-33%)
  - Memória: 320 MB (-64%)
```

## Otimizacao 2: Parser Otimizado

Em vez de usar `std.fmt.parseFloat`, que e generico, escrevemos um parser especializado para nosso formato:

```zig
/// Parser rapido de inteiros — sem tratamento de erros generico
fn parseIntFast(str: []const u8) u16 {
    var resultado: u16 = 0;
    for (str) |c| {
        resultado = resultado * 10 + @as(u16, c - '0');
    }
    return resultado;
}

/// Parser rapido de float simples (formato: DDDD.DD)
fn parseFloatFast(str: []const u8) f64 {
    var parte_inteira: u64 = 0;
    var parte_decimal: u64 = 0;
    var casas_decimais: u32 = 0;
    var no_decimal = false;

    for (str) |c| {
        if (c == '.') {
            no_decimal = true;
            continue;
        }
        if (no_decimal) {
            parte_decimal = parte_decimal * 10 + (c - '0');
            casas_decimais += 1;
        } else {
            parte_inteira = parte_inteira * 10 + (c - '0');
        }
    }

    var divisor: f64 = 1.0;
    for (0..casas_decimais) |_| divisor *= 10.0;

    return @as(f64, @floatFromInt(parte_inteira)) +
        @as(f64, @floatFromInt(parte_decimal)) / divisor;
}

/// V3: parser otimizado com busca manual de delimitadores
fn parseLineV3(linha: []const u8) !LogEntry {
    // Buscar espacos manualmente — mais rapido que split iterator
    var espacos: [4]usize = undefined;
    var count: usize = 0;

    for (linha, 0..) |c, i| {
        if (c == ' ') {
            if (count >= 4) break;
            espacos[count] = i;
            count += 1;
        }
    }

    if (count < 4) return error.FormatoInvalido;

    return .{
        .ip = linha[0..espacos[0]],
        .metodo = linha[espacos[0] + 1 .. espacos[1]],
        .url = linha[espacos[1] + 1 .. espacos[2]],
        .status = parseIntFast(linha[espacos[2] + 1 .. espacos[3]]),
        .tempo_ms = parseFloatFast(linha[espacos[3] + 1 ..]),
    };
}
```

```
V3 (Parser otimizado): 10M linhas em 1.600 ms (-43%)
  - Parse: 450 ms (-75% vs V1)
```

## Otimizacao 3: Contagem com Array Fixo

Para status codes (que sao um conjunto pequeno e previsivel), substituir HashMap por array direto:

```zig
const EstatisticasV4 = struct {
    total_requests: u64 = 0,
    soma_tempo: f64 = 0,
    // Array direto indexado por status code (100-599)
    contagem_status: [600]u64 = [_]u64{0} ** 600,
    contagem_ips: std.StringHashMap(u64),
    allocator: std.mem.Allocator,

    pub fn registrar(self: *EstatisticasV4, entry: LogEntry) !void {
        self.total_requests += 1;
        self.soma_tempo += entry.tempo_ms;

        // Acesso direto — O(1), sem hash, sem colisao
        self.contagem_status[entry.status] += 1;

        // IPs ainda usam hashmap
        const result = try self.contagem_ips.getOrPut(entry.ip);
        if (!result.found_existing) result.value_ptr.* = 0;
        result.value_ptr.* += 1;
    }
};
```

```
V4 (Array para status): 10M linhas em 1.200 ms (-25%)
```

## Otimizacao 4: Processamento SIMD para Busca de Newlines

Encontrar quebras de linha com SIMD:

```zig
/// Encontrar proxima newline usando SIMD
fn encontrarNewline(dados: []const u8) ?usize {
    const vec_len = 32;
    const newline_vec: @Vector(vec_len, u8) = @splat('\n');

    var i: usize = 0;
    while (i + vec_len <= dados.len) : (i += vec_len) {
        const bloco: @Vector(vec_len, u8) = dados[i..][0..vec_len].*;
        const mask = bloco == newline_vec;

        // Encontrar primeiro bit setado
        const bits: u32 = @bitCast(mask);
        if (bits != 0) {
            return i + @ctz(bits);
        }
    }

    // Tail loop
    while (i < dados.len) : (i += 1) {
        if (dados[i] == '\n') return i;
    }

    return null;
}

/// V5: split de linhas com SIMD
fn analisarV5(dados: []const u8, allocator: std.mem.Allocator) !EstatisticasV4 {
    var stats = EstatisticasV4{
        .contagem_ips = std.StringHashMap(u64).init(allocator),
        .allocator = allocator,
    };

    var pos: usize = 0;
    while (pos < dados.len) {
        const restante = dados[pos..];
        const fim_linha = encontrarNewline(restante) orelse restante.len;

        if (fim_linha > 0) {
            const entry = parseLineV3(restante[0..fim_linha]) catch {
                pos += fim_linha + 1;
                continue;
            };
            try stats.registrar(entry);
        }
        pos += fim_linha + 1;
    }

    return stats;
}
```

```
V5 (SIMD newline scan): 10M linhas em 850 ms (-29%)
```

## Resultados Consolidados

| Versao | Tempo | Speedup | Memoria | Tecnica |
|--------|-------|---------|---------|---------|
| V1 | 4.200 ms | 1.0x | 890 MB | Ingênua |
| V2 | 2.800 ms | 1.5x | 320 MB | String interning |
| V3 | 1.600 ms | 2.6x | 320 MB | Parser otimizado |
| V4 | 1.200 ms | 3.5x | 315 MB | Array para status |
| V5 | 850 ms | 4.9x | 315 MB | SIMD newline scan |

**Resultado final: 4.9x mais rapido, 65% menos memoria.**

## Quando Parar de Otimizar

### Sinais de que Voce Deve Parar

1. **O codigo esta rapido o suficiente** para os requisitos
2. **O gargalo mudou** para I/O, rede ou banco de dados
3. **A legibilidade esta comprometida** demais
4. **Os ganhos sao marginais** (otimizar de 850ms para 820ms raramente justifica)

### Trade-offs

```zig
// Legivel mas lento (V1):
var partes = std.mem.splitScalar(u8, linha, ' ');
const ip = partes.next() orelse return error.FormatoInvalido;

// Rapido mas menos legivel (V3):
var espacos: [4]usize = undefined;
var count: usize = 0;
for (linha, 0..) |c, i| {
    if (c == ' ') { espacos[count] = i; count += 1; if (count >= 4) break; }
}
```

A decisao de qual versao usar depende do contexto:
- **Script de uso ocasional**: V1 e perfeito
- **Servico de producao com alto volume**: V5 justifica a complexidade
- **Biblioteca publica**: V3 oferece bom equilibrio

## Checklist de Otimizacao

1. **Medir** — Profile primeiro, identifique o hotspot real
2. **Algoritmo** — A complexidade algoritmica e correta?
3. **Alocacoes** — Pode reutilizar memoria em vez de alocar?
4. **Layout** — Os dados estao cache-friendly (SoA)?
5. **Branches** — Pode eliminar condicionais do hot path?
6. **SIMD** — O loop processa dados independentes?
7. **Medir de novo** — A otimizacao realmente melhorou?

## Conclusao

Otimizacao de performance e um processo iterativo e mensuravel. Nesta serie, aprendemos a medir corretamente com benchmarking, organizar dados para maximizar cache hits, usar SIMD para processamento paralelo, e encontrar gargalos com ferramentas de profiling. O estudo de caso demonstrou que melhorias de 5x sao alcancaveis com tecnicas sistematicas em Zig.

## Serie Completa

Esta e a conclusao da serie **Otimizacao de Performance em Zig**:

1. [Tecnicas de Benchmarking](/tutoriais/zig-performance/artigo-1-benchmarking/)
2. [Codigo Cache-Friendly](/tutoriais/zig-performance/artigo-2-cache-friendly/)
3. [SIMD e Vetorizacao](/tutoriais/zig-performance/artigo-3-simd-vetorizacao/)
4. [Ferramentas de Profiling](/tutoriais/zig-performance/artigo-4-profiling-tools/)
5. **Otimizacao Real: Estudo de Caso** (este artigo)

## Conteudo Relacionado

- [Zig em Fintech e Trading](/cases/case-zig-fintech/) — Performance em producao
- [Zig em Telecomunicacoes](/cases/case-zig-telecom/) — Alta performance em telecom
- [Masterclass Memoria](/tutoriais/zig-memoria-masterclass/) — Gerenciamento avancado
- [Clean Code em Zig](/artigos/zig-clean-code/) — Equilibrio legibilidade-performance
- [Estrategias de Alocacao](/artigos/zig-alocacao-memoria-estrategias/) — Memoria avancada
