Otimização de baixo nível e Zig
(alloc.dev)- A otimização de baixo nível pode ser implementada com facilidade na linguagem Zig
- O compilador realiza bem a otimização na maioria das situações, mas às vezes é preciso transmitir com clareza a intenção do programador para obter um desempenho melhor
- Zig oferece geração de código de alto desempenho e metaprogramação poderosa com o recurso de execução em tempo de compilação (
comptime) - Em comparação com Rust, Zig permite otimizações mais precisas por meio de anotações e de uma estrutura de código explícita
- Em operações repetitivas como comparação de strings, é possível usar
comptimepara gerar código assembly superior ao de funções comuns
Otimização e Zig
Como diz o famoso alerta: "Tudo é possível, mas o que é interessante não se consegue com facilidade." A otimização de programas sempre foi uma preocupação central dos desenvolvedores. Para reduzir custos de infraestrutura em nuvem, melhorar latência e simplificar sistemas, a otimização de código é indispensável. Este texto explica os conceitos de otimização de baixo nível em Zig e destaca os pontos fortes da linguagem.
Podemos confiar no compilador?
- Em geral, há muito o conselho de "confie no compilador", mas na prática há casos em que o compilador se comporta de forma diferente do esperado ou viola a especificação da linguagem
- Linguagens de alto nível têm limitações de desempenho porque é difícil transmitir com clareza a intenção (
intent) - Linguagens de baixo nível, por causa da explicitude do código, permitem que o compilador conheça as informações necessárias para otimização; por exemplo, ao comparar a função
maxArrayem JavaScript e em Zig, Zig informa em tempo de compilação — e não em tempo de execução — tipo, alinhamento, possibilidade de alias etc. - Ao escrever a mesma operação
maxArrayem Zig e Rust, obtém-se um código assembly de alto desempenho quase idêntico, mas quanto melhor a intenção for expressa, melhor tende a ser o resultado da otimização - Ainda assim, nem sempre dá para confiar no desempenho do compilador, então, em trechos críticos, é preciso verificar diretamente o código e o resultado da compilação e buscar formas de otimização
O papel do Zig
- Zig, com sua explicitude precisa e suas ricas funções embutidas, ponteiros e anotações,
comptimee um comportamento ilegal bem definido, consegue produzir código otimizado sem depender de informação abstrata - Em Rust, o modelo de memória garante por padrão a ausência de alias entre argumentos, mas em Zig é preciso usar anotações como
noaliasexplicitamente - Se o critério for apenas LLVM IR, o nível de otimização do Zig também é elevado
- Acima de tudo, o
comptime(execução em tempo de compilação) do Zig é uma poderosa ferramenta de otimização
O que é comptime?
- O
comptimedo Zig é usado para geração de código, incorporação de valores constantes e criação de estruturas genéricas baseadas em tipos, desempenhando papel importante na melhora do desempenho em tempo de execução - É possível implementar metaprogramação com
comptime - Diferentemente de macros em C/C++ ou do sistema de macros de Rust,
comptimenão usa uma sintaxe separada: ele é código comum - O código de
comptimenão altera diretamente a AST e permite inspecionar, refletir e gerar elementos para todos os tipos em tempo de compilação - A flexibilidade de
comptimeinfluenciou melhorias em outras linguagens, como Rust, e está integrada ao Zig de forma natural
Limites de comptime
- Alguns recursos de macro, como token-pasting, não podem ser substituídos pelo
comptimedo Zig - Como Zig valoriza a legibilidade do código, não permite criar variáveis fora do escopo nem definir macros desse tipo
- Em compensação, o
comptimedo Zig oferece vários usos amplos de metaprogramação, como reflexão de tipos, implementação de DSLs e otimização de parsing de strings
Otimizando comparação de strings com comptime
- Uma função comum de comparação de strings pode ser implementada em qualquer linguagem, mas em Zig, quando uma das duas strings é uma constante conhecida em
comptime, é possível gerar código assembly mais eficiente - Por exemplo, se uma string for sempre
"Hello!\n", é possível aplicar uma otimização que a compara em blocos maiores, e não byte a byte - Para isso, usando
comptime, pode-se gerar em tempo de compilação código de alto desempenho com vetores SIMD, processamento em blocos e otimização dos bytes restantes - Com esse método, além de comparações repetitivas de strings, também é possível implementar código orientado a desempenho para diversos mapeamentos baseados em dados estáticos, tabelas de hash perfeitas, parsers de AST e muito mais
Conclusão
- Zig é extremamente adequado para otimização de baixo nível e, graças à estrutura explícita do código e ao poderoso
comptime, permite implementar diretamente o mais alto nível de desempenho - Mesmo em comparação com outras linguagens, como Rust, a capacidade de programação em tempo de compilação e a explicitude do Zig representam uma grande vantagem no desenvolvimento de software de alto desempenho
- A capacidade de otimização do Zig tende a se tornar uma vantagem competitiva cada vez mais importante
1 comentários
Comentários no Hacker News
buna ponto de ficar impressionado.bundeixou minha vida muito mais prática.uv, feito em Rust, proporciona uma experiência parecidafor(;;);realmente deve ser infinito, eloop {}em Rust também deveria ser. Mas os desenvolvedores do LLVM às vezes agem como se estivessem fazendo apenas um compilador de C++, então mesmo quando Rust pede "por favor, faça um loop infinito", o LLVM aplica algo como "isso não acontece em C++ segundo a especificação, então vou otimizar!", o que causa problemas. Foi uma otimização errada aplicada à linguagem erradacomptime, fazer comparação de strings inline e desenrolada é algo perfeitamente possível em C. Exemplo relacionadogenericem que vários tipos podem aparecer em tempo de execução. Por isso, em JS, se você fornecer bem a informação de tipo, o JIT consegue gerar loops muito mais rápidos (ainda que talvez não chegue à vetorização). Na prática, TypedArray não é usado com tanta frequência porque o custo de inicialização é alto, e só vale a pena quando há muita reutilização. E o texto dizia que o código JS estava inflado, mas grande parte disso vem de guards inseridos porque o JIT não pode confiar na verificação do comprimento do array; na prática, todo mundo escreve loops comoi < x.length, o que permite otimização do JIT. Nesse sentido é um pouco preciosismo, mas é uma diferença pequenacomptimeque um compilador C++ não consegue preverpurchase.calculate_tax().await.map_err(|e| TaxCalculationError { source: e })?;está cheio de intenção, mas é impossível prever como o código de máquina vai sair na práticaannotation noise) — especialmente em casts explícitos de inteiros em expressões matemáticas. Veja este texto. Em termos de desempenho, quando Zig é mais rápido que C, muitas vezes é porque o Zig usa configurações mais agressivas de otimização do LLVM (-march=native, otimização de programa inteiro etc.). Na prática, em C também dá para usar dicas de otimização comounreachablepor meio de extensões da linguagem, e o Clang também faz constant folding de forma bem agressiva. Ou seja, a diferença entrecomptimeem Zig e codegen em C muitas vezes vem das configurações de otimização do compilador. TL;DR: se C estiver lento, primeiro verifique a configuração do compilador. No fim das contas, o coração da otimização é o LLVMcomptimee compilação de programa inteiro do que eu esperava no artigo original. Concordo com isso. Como referência, Virgil já oferecia uso da linguagem inteira em tempo de compilação e suporte a compilação de programa inteiro desde 2006. Virgil não tem LLVM como alvo, então comparação de velocidade acaba sendo comparação de backend. Graças a essa abordagem, Virgil consegue fazer otimizações muito fortes, como devirtualizar chamadas de método antecipadamente, remover ao máximo campos/objetos não usados, propagar constantes até objetos de heap baseados em campos e especializar tudo de forma completaforno Zig bagunçada demais. Ter que colocar duas listas lado a lado e alinhar suas posições já dói só de olhar. Acho um erro recente das linguagens despejar sintaxe "mágica" e símbolos especiais demais. Não parece algo para ficar olhando por horas