2 pontos por GN⁺ 2025-10-28 | 1 comentários | Compartilhar no WhatsApp
  • O autor, enquanto aprendia a linguagem Zig e trabalhava no projeto de reescrita do índice do AcoustID, tentou uma nova abordagem ao esbarrar nas limitações da programação de rede
  • Para implementar em Zig o modelo de I/O assíncrono e concorrência que já usava em C++ e Go, decidiu desenvolver sua própria biblioteca
  • Como resultado, criou a biblioteca Zio, que implementa em Zig um modelo de concorrência no estilo do Go, permitindo escrever código assíncrono com aparência de código síncrono, sem callbacks
  • O Zio oferece suporte a I/O assíncrono de rede e arquivos, canais, primitivas de sincronização e monitoramento de sinais, e apresenta desempenho superior ao Go ou ao Tokio do Rust no modo single-thread
  • Este projeto mostra a possibilidade de combinar o desempenho de nível de sistema do Zig com modelos modernos de concorrência, sendo visto como um ponto de virada importante para a expansão do ecossistema Zig

A linguagem Zig e a motivação inicial

  • O autor já observava o Zig, originalmente projetado como uma linguagem de baixo nível para software de áudio, mas não sentia uma necessidade prática imediata
    • Passou a se interessar ao ver o caso em que Andrew Kelley, criador do Zig, reimplementou em Zig o algoritmo Chromaprint do autor
  • Conduziu o projeto de reescrita do índice invertido do AcoustID como uma oportunidade para aprender Zig e, como resultado, obteve uma implementação mais rápida e mais escalável do que a versão em C++
  • Porém, ao adicionar a interface de servidor, encontrou o problema da falta de suporte adequado a rede assíncrona

Abordagens anteriores e limitações

  • Na versão anterior em C++, o framework Qt era usado para tratar I/O assíncrono; embora baseado em callbacks, era viável graças ao suporte abrangente
  • Em protótipos posteriores, aproveitou a conveniência da linguagem Go para rede e concorrência, mas em Zig faltava um nível semelhante de abstração
  • Para implementar em Zig um servidor TCP e a camada de cluster, acabava surgindo a ineficiência de ter de criar muitas threads
    • Para resolver isso, escreveu diretamente um cliente Zig para o sistema de mensageria NATS (nats.zig) e explorou em profundidade os recursos de rede do Zig

O surgimento da biblioteca Zio

  • Com base nessa experiência, publicou o Zio: biblioteca de I/O assíncrono e concorrência para Zig
  • O objetivo do Zio é permitir escrever código assíncrono sem callbacks; internamente o I/O assíncrono funciona normalmente, mas externamente a estrutura se parece com código síncrono
  • Implementa de forma limitada um modelo de concorrência no estilo do Go adaptado ao Zig
    • As tarefas do Zio assumem a forma de corrotinas stackful com pilha de tamanho fixo
    • Ao chamar stream.read(), a operação de I/O é executada em segundo plano e, quando termina, a tarefa é retomada para retornar o resultado
  • Essa abordagem oferece ao mesmo tempo simplificação do gerenciamento de estado e melhor legibilidade do código

Conjunto de recursos e estrutura do runtime

  • O Zio suporta I/O assíncrono completo de rede e arquivos, primitivas de sincronização (mutex, variável de condição etc.), canais no estilo do Go e monitoramento de sinais do sistema operacional
  • As tarefas podem ser executadas em modo single-thread ou multi-thread
    • No modo multi-thread, as tarefas podem ser movidas entre threads, trazendo menor latência e melhor balanceamento de carga
  • Implementa a interface padrão Reader/Writer, garantindo compatibilidade com bibliotecas externas

Desempenho e comparação

  • O autor ainda não publicou benchmarks oficiais, mas afirma ter confirmado desempenho superior ao Go e ao Tokio do Rust no modo single-thread
  • O custo de troca de contexto é tão baixo quanto uma chamada de função, oferecendo uma velocidade de troca praticamente gratuita
  • O modo multi-thread ainda não é tão robusto quanto Go/Tokio, mas mostra desempenho semelhante ou ligeiramente superior
    • No futuro, a adição de recursos de fairness pode reduzir um pouco esse desempenho

Código de exemplo e uso

  • A documentação inclui um exemplo de servidor HTTP baseado em Zio
    • Usa zio.net.Stream para aceitar conexões e tratar cada conexão em uma tarefa separada
    • zio.Runtime gerencia a execução das tarefas e o agendamento de I/O
  • Essa estrutura permite escrever I/O assíncrono como se fosse código síncrono, além de possibilitar controle de fluxo claro e gerenciamento da liberação de recursos

Planos futuros e significado

  • Por meio do Zio, o autor concluiu que o Zig pode ir além de ser apenas uma linguagem para código de sistema de alto desempenho e evoluir para uma linguagem completa para desenvolvimento de aplicações de rede
  • Como próximo passo, planeja reescrever o cliente NATS com base no Zio e desenvolver uma biblioteca cliente/servidor HTTP baseada em Zio
  • O projeto é visto como uma iniciativa que impulsiona a expansão da infraestrutura de rede e concorrência do ecossistema Zig, além de representar uma tentativa de construir um modelo moderno de runtime comparável ao de Go ou Rust

1 comentários

 
GN⁺ 2025-10-28
Comentários do Hacker News
  • Dizem que a troca de contexto é quase gratuita no nível de chamada de função, mas na prática há custos sutis, como quebrar o branch predictor
    Não está claro se o design de async do Zig usa pares de call/return do hardware ou se é traduzido com base em saltos indiretos
    Para fazer um benchmark realmente correto, seria preciso comparar o tempo total de execução entre um programa com troca contínua entre duas tarefas e um programa totalmente síncrono. Isso é bem complicado
    • Em corrotinas stackless, se você continuar alternando entre duas tarefas no fundo da pilha de chamadas e o código de troca de pilha estiver inline, dá para evitar em grande parte a penalidade de incompatibilidade call/ret
      Se você puder controlar o compilador, também é possível trocar call/ret do código de I/O por saltos explícitos
      No longo prazo, seria desejável que as CPUs adotassem um meta-preditor para prever melhor corrotinas stackful
    • No momento, o Zig não tem mais async no nível da linguagem, e o autor do post implementou ele mesmo a troca de tarefas em espaço de usuário
    • Quando fiz um teste simples de ping-pong entre corrotinas, já obtive números difíceis de acreditar em comparação com outras soluções
    • Um novo async deve ser adicionado ao Zig em breve, então estou esperando antes de me aprofundar de vez
      Texto relacionado: Zig new async I/O
  • Corrotinas stackful fazem sentido quando há RAM suficiente
    Eu uso Zig em ambiente embarcado (ARM Cortex-M4, 256KB de RAM), e o utilizo para garantir segurança de memória na interoperabilidade com C
    Prefiro um async com cores, como no Rust. Gosto dessa sensação mágica de parecer código síncrono, mas em codebases grandes o problema é ficar difícil distinguir quais funções fazem blocking
    • Na verdade, todo código síncrono é uma ilusão criada pelo software
      A CPU não bloqueia de fato em I/O, e as próprias threads do sistema operacional são corrotinas stackful implementadas pelo SO
      No nível da linguagem, só é possível implementar essa ilusão de forma mais eficiente; a essência é a mesma
    • O novo IO do Zig deve ter uma estrutura colorida de forma mais elegante que a do Rust
      A cor é determinada por a função realizar ou não I/O, e no ponto de chamada fica explícito se é async ou não
      O Zig também pretende calcular o tamanho de pilha necessário na chamada de função, então a expectativa é reduzir o desperdício de RAM das corrotinas stackful
    • É justamente por isso que o Zig quer expressar I/O de forma explícita: para permitir rastrear quais funções fazem blocking
  • Há quem ache cedo demais para adotar Zig agora. O modelo de I/O está mudando bastante, e a impressão é que isso ainda vai levar alguns anos
    • Eu também abandonei o Zig em 2020 por motivo parecido.
      Mesmo assim, o projeto continua muito ativo, e vejo de forma positiva o fato de priorizar o design correto acima de releases rápidas
      Por enquanto uso Go ou C enquanto espero a 1.0
    • Alguns anos passam rápido. O Zig já é uma linguagem boa o bastante para uso. Quem quiser usar, usa; quem não quiser, não usa
    • Na prática, este realmente é um momento ruim. Há uma grande mudança de I/O prevista para a 0.16, e nem o próprio autor ainda está usando os recursos mais novos
      Eu também pretendo esperar a 0.16 para trabalhos focados em I/O
    • Ainda assim, se for trabalho relacionado a I/O, usar a interface de reader/writer com buffer do Zig 0.15 não deve trazer grandes mudanças
    • Eu penso o contrário: agora não é um momento ruim. O Zig não está mudando radicalmente como linguagem, e sim adicionando uma nova e poderosa API std.Io
      O código existente continua funcionando, e a nova API é mais ergonômica e performática
      Eu mesmo migrei um projeto antigo para a nova API de Reader/Writer e o código ficou muito mais limpo
  • Ainda me pergunto por que async baseado em callback virou o padrão
    Uma abordagem como a do libtask parece muito mais limpa
    O Rust também adotou async baseado em callback, e eu não entendo bem por quê
    Referência: libtask
    • Corrotinas stackless podem ser implementadas dentro da própria linguagem, e a vantagem é a interação previsível com recursos já existentes
      Mas mexer diretamente com a pilha pode entrar em conflito com tratamento de exceções, GC, depuradores etc.
      Também é difícil incorporar esse tipo de mudança no nível do LLVM, então, do ponto de vista de quem projeta a linguagem, existem muitas restrições práticas
    • Em pesquisas da Microsoft para o padrão C++, chegou-se à conclusão de que corrotinas stackless têm overhead de memória muito menor e oferecem mais liberdade no desenho do executor
    • A desvantagem de abordagens como zio ou libtask é que é preciso estimar manualmente o tamanho da pilha
      Se for pequeno demais, ocorre overflow; se for grande demais, há desperdício de memória
      O tamanho de pilha necessário varia entre plataformas, então também há problemas de portabilidade
      Quando a issue #157 do Zig for resolvida, essa abordagem deve melhorar
    • Em casos como o do libtask, o tamanho da pilha da thread é incerto e muito maior que o estado async comum
    • O async do Rust não é callback, e sim baseado em polling
      Ou seja, existem três formas de implementar async
      1. baseado em callback (Node.js, Swift)
      2. baseado em stackful (Go, libtask)
      3. baseado em polling (Rust)
        O Rust converte isso em máquinas de estado estáticas, e o runtime faz o polling
        O modelo stackful desperdiça muita memória e dificulta gerenciar o tamanho da pilha
        Para evitar isso, o Rust adotou uma estrutura stackless, e o Zig deve permitir escolher entre os dois modelos
        Referência: código de corrotinas do zio
  • Como uma leitura TCP pode ficar bloqueada por um mês, fico curioso sobre como seria a interface de timeout de I/O
    • Em sockets TCP, é possível definir timeouts de leitura/escrita com setsockopt
      O Zig fornece uma camada de API POSIX
      Referência: documentação do setsockopt
    • No momento, o std.Io.Reader do Zig não reconhece timeouts
      Estou imaginando uma estrutura que funcione como o asyncio.timeout do Python
      Exemplo de código:
      var timeout: zio.Timeout = .init;
      defer timeout.cancel(rt);
      timeout.set(rt, 10);
      const n = try reader.interface.readVec(&data);
      
    • A maioria dos frameworks async ignora timeouts e cancelamento
      Na verdade, essa é a parte mais difícil
  • No Scala já existe uma biblioteca de concorrência chamada ZIO
    Referência: zio.dev
  • Recentemente fiquei impressionado com o Tokio do Rust, e se no Zig for possível implementar concorrência no estilo Go sem GC, eu com certeza quero experimentar
    • O Go consegue usar truques como pilhas expansíveis sem limite fixo graças ao GC
      Mas o Zig, apesar de ser uma linguagem de baixo nível, me impressionou por conseguir expressar APIs de alto nível de forma elegante
  • Conheci o Zig pela primeira vez no site do Bun. Ele realmente vem evoluindo muito rápido ultimamente
  • Na versão antiga em C++, eu implementava I/O assíncrono com Qt, mas desta vez migrei para Go
    Tanto Zig quanto Go ganharam novos bindings para Qt
    • Go: miqt
    • Zig: libqt6zig
      Eu queria bindings para Rust. O cxx-qt é o único projeto que parece continuar mantido, mas eu não quero usar QML nem CMake. Quero usar Qt apenas com Rust + Cargo
  • No Scala também já existe o framework famoso ZIO, então dá para ver como é difícil escolher nomes