Perguntas de Entrevista sobre Performance em Zig

Perguntas de Entrevista sobre Performance em Zig

Performance é a razão principal pela qual empresas escolhem Zig. Entrevistas para posições que envolvem Zig frequentemente incluem perguntas profundas sobre otimização, profiling e técnicas de alta performance. Estas perguntas são especialmente relevantes para vagas em fintech, infraestrutura e gaming.

Fundamentos

Por que Zig é eficiente em termos de performance?

  • Sem GC: Não há pausas de garbage collection. Gerenciamento de memória é explícito via allocators.
  • Sem runtime: O binário é código puro — sem interpretador, JIT ou VM.
  • Sem alocações ocultas: Você sabe exatamente quando e onde memória é alocada.
  • Compilação nativa via LLVM: Acesso a todas as otimizações do LLVM.
  • Controle de layout de memória: Packed structs, align, controle de padding.
  • SIMD nativo: Vetores SIMD são tipos de primeira classe.

Explique o conceito de cache-friendly programming.

CPUs modernas acessam memória principal em blocos (cache lines, tipicamente 64 bytes). Código cache-friendly organiza dados para maximizar cache hits:

Array of Structs (AoS) vs Struct of Arrays (SoA):

// AoS — ruim para cache se só acessa position
const EntityAoS = struct { position: Vec3, velocity: Vec3, health: f32 };
var entities_aos: [1000]EntityAoS = undefined;

// SoA — bom para cache quando acessa apenas positions
const EntitiesSoA = struct {
    positions: [1000]Vec3,
    velocities: [1000]Vec3,
    healths: [1000]f32,
};

SoA é comum em game engines e processamento de dados, onde frequentemente iteramos sobre um único campo de todos os elementos.

O que é zero-copy e como Zig facilita?

Zero-copy significa processar dados sem copiá-los — operando diretamente nos buffers originais. Zig facilita com slices:

fn processarMensagem(buffer: []const u8) !Mensagem {
    // Slice do buffer original — sem cópia
    const header = buffer[0..8];
    const payload = buffer[8..];
    return Mensagem{ .header = header, .payload = payload };
}

Isso é crítico em networking de alta performance e processamento de dados.

Perguntas Avançadas

Como usar SIMD em Zig?

Zig tem suporte nativo a vetores SIMD:

const Vec4f = @Vector(4, f32);

fn somar(a: Vec4f, b: Vec4f) Vec4f {
    return a + b; // operação SIMD — 4 somas em uma instrução
}

O compilador pode também auto-vetorizar loops simples quando otimizações estão ativas.

Quais ferramentas de profiling são usadas com Zig?

  • perf (Linux): Profiling de CPU, cache misses, branch prediction
  • Valgrind/Callgrind: Profiling de chamadas e memória
  • Tracy: Profiler de frames para game dev
  • Ferramentas do ecossistema: Ferramentas específicas para Zig
  • std.time.Timer: Timing manual para microbenchmarks

Como reduzir branch mispredictions?

  • Organizar dados para que branches sejam previsíveis
  • Usar lookup tables em vez de cascatas de if/else
  • Branchless programming com operações condicionais
  • Profile-guided optimization (PGO)

Como escrever benchmarks confiáveis em Zig?

const std = @import("std");

pub fn main() !void {
    const iteracoes: u64 = 1_000_000;

    var timer = try std.time.Timer.start();
    var i: u64 = 0;
    var soma: u64 = 0;
    while (i < iteracoes) : (i += 1) {
        soma += funcaoParaBenchmark(i);
    }
    const elapsed = timer.read();

    // Usar o resultado para evitar que o compilador elimine o código
    std.debug.print("Soma: {d}\n", .{soma});
    std.debug.print("Tempo: {d}ns ({d}ns por iteração)\n", .{
        elapsed,
        elapsed / iteracoes,
    });
}

Boas práticas para benchmarks em Zig: sempre use ReleaseFast ou ReleaseSafe (nunca Debug), faça “warmup” executando algumas iterações antes de medir, use o resultado da função para evitar que o compilador otimize o código inteiro, e execute múltiplas vezes para obter média e desvio padrão. O compilador Zig com LLVM é muito agressivo em otimizações — um benchmark que não usa o resultado pode ser eliminado completamente.

O que são packed struct e extern struct e quando usá-los?

// packed struct — sem padding, layout bit a bit
const Flags = packed struct {
    leitura: bool,
    escrita: bool,
    execucao: bool,
    _padding: u5 = 0,
}; // 1 byte total

// extern struct — layout compatível com C (com padding do ABI C)
const PontoC = extern struct {
    x: f32,
    y: f32,
    z: f32,
}; // 12 bytes, compatível com structs C

// Regular struct — Zig pode reordenar campos para melhor layout
const Ponto = struct {
    x: f32,
    y: f32,
    z: f32,
};

Use packed struct para flags de bits, registradores de hardware e protocolos de rede com layout fixo. Use extern struct para interoperabilidade com C. Para código puro Zig, use structs regulares e deixe o compilador otimizar o layout. Em entrevistas de performance, demonstrar conhecimento de padding e alinhamento mostra profundidade técnica.

Como evitar alocações em hot paths?

A técnica mais poderosa para performance é usar std.mem.Allocator com um FixedBufferAllocator para alocações temporárias em caminhos críticos:

fn processarMensagem(dados: []const u8) !void {
    // Buffer na stack — sem alocação de heap
    var buf: [4096]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    const temp_alloc = fba.allocator();

    // Alocações temporárias vão para a stack
    const temp = try temp_alloc.alloc(u8, dados.len);
    @memcpy(temp, dados);
    // ... processar ...
    // Memória liberada automaticamente quando buf sai de escopo
}

Essa técnica é usada extensivamente em parsers e servidores de alta performance em Zig, onde cada alocação de heap em um hot path pode adicionar dezenas de nanossegundos de latência.

Preparação

Combine com perguntas de memória (alocação afeta performance), concorrência (paralelismo para throughput) e desafios de código. Explore ferramentas de profiling do ecossistema e veja cases de produção para contexto real.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.