2 pontos por GN⁺ 2023-07-13 | 1 comentários | Compartilhar no WhatsApp
  • O protótipo de empréstimo por região do Vale conseguiu compilar com sucesso pela primeira vez, permitindo validar em programas reais uma abordagem de segurança de memória que combina referências geracionais e regiões
  • Desenvolvedores podem escrever código de forma próxima de C/C++, aplicando pure e empréstimo por região apenas onde for necessário para reduzir a sobrecarga das verificações geracionais
  • O primeiro programa zero-check em Vale foi um exemplo de Cellular Automata para geração de níveis de roguelike, e o assembly resultante chegou a um nível quase idêntico ao modo unsafe_with_bounds
  • Nos benchmarks, safe_fastest não apresentou slowdown observável em relação a unsafe_with_bounds, enquanto unsafe_no_bounds foi 1.18 ± 0.01 vezes mais rápido que os dois modos
  • Ainda não se trata de uma comparação direta com C/Rust; ainda restam um pre-optimizer específico para Vale e a organização dos recursos de região por causa de ruído de otimização do LLVM, maturidade do protótipo e falta de suporte a inline data

Combinação de referências geracionais e empréstimo por região

  • A abordagem de segurança de memória do Vale busca não usar reference counting, tracing garbage collection nem borrow checking
  • A estrutura básica é que o desenvolvedor escreve o programa de forma próxima de C ou C++, e as generational references do Vale mantêm a segurança de memória
  • Depois, ao aplicar pure e region borrowing, é possível remover a maior parte da sobrecarga das verificações geracionais
  • Com o estilo linear adicionado, acredita-se que as verificações geracionais no código Vale possam cair a zero
  • O empréstimo por região é totalmente opt-in, então é possível primeiro escrever com mais liberdade e depois adicionar isso apenas às partes que precisarem de otimização
  • A proposta é permitir que, dentro do mesmo programa, algumas partes sejam flexíveis como Java, outras rápidas como Rust, ou qualquer ponto intermediário

O que foi necessário para criar o protótipo

  • Nos últimos anos, a base do compilador foi construída para dar suporte ao mesmo tempo ao sistema de empréstimo por região e às generational references
  • Além da complexidade do próprio sistema de empréstimo, também foi necessário ter full generics mais fortes do que os templates existentes
  • Para que regiões e referências geracionais funcionassem juntas de forma natural, também foi preciso adicionar uma nova etapa ao compilador
    • Internamente, as regiões são reduzidas a inteiros de “pure height”
    • O parâmetro genérico de região é representado por um número negativo, a região padrão por 0, e cada bloco pure por números positivos crescentes
  • Há alguns meses, o protótipo de regiões ficou pronto e, embora ainda esteja bruto, conseguiu compilar algo com sucesso pela primeira vez
  • Como resultado, foi criado o primeiro programa zero-check em Vale
    • A flag do compilador --print_mem_overhead true pode contar o número de verificações geracionais do programa

O primeiro programa zero-check e a comparação de assembly

  • O primeiro programa foi um exemplo de Cellular Automata para gerar níveis de um jogo roguelike
  • Mesmo pequenos erros no código do compilador podem adicionar instruções extras ao assembly resultante e criar uma sobrecarga artificial no programa final
  • Para rastrear isso, o assembly resultante foi comparado continuamente com os modos unsafe do Vale
    • unsafe_no_bounds: desativa todas as proteções de segurança de memória, de forma semelhante a C, e usa raw pointers em vez de generational references
    • unsafe_with_bounds: adiciona bounds checking em acessos a arrays, como em Rust
  • Depois de rastrear as diferenças por alguns meses, o assembly resultante ficou quase idêntico ao modo unsafe_with_bounds
  • A única diferença esperada era inserir um número de geração pseudoaleatório no topo de cada allocation, e ele não era lido nas verificações geracionais reais
    • Internamente, é usado um registrador monotonicamente crescente para manter isso rápido
    • Essa diferença pode ser removida quando isolates ou unique references forem adicionados

Resultados dos benchmarks e condições de medição

  • O resumo do benchmark é o seguinte
Summary
  './build_unsafe_no_bounds/main' ran
    1.18 ± 0.01 times faster than './build_unsafe_with_bounds/main'
    1.18 ± 0.01 times faster than './build_safe_fastest/main'
  • O modo normal do Vale, safe_fastest, não mostrou slowdown em comparação com o modo que tem apenas bounds checking
  • Nessa medição, essa abordagem teve sobrecarga não observável
  • Para testar diretamente, é possível compilar a branch regions, consultar os scripts de benchmarking e tirar dúvidas no servidor do Discord
  • As condições da medição têm limitações claras
    • Não é um benchmark com comparação direta com linguagens como C ou Rust
    • Esses compiladores têm anos de otimizações independentes, o que pode confundir as variáveis do experimento
    • Para isolar a diferença da abordagem de segurança de memória, a comparação foi feita com unsafe_no_bounds e unsafe_with_bounds
    • O ambiente de medição foi um Razer Blade 15" 2018 com SSD de 512GB rodando Ubuntu 22.04
    • A ferramenta de medição foi o hyperfine, executado dentro de um cset shield

Ruído de otimização observado em programas maiores

  • Em programas maiores, foi observado bastante optimizer noise
    • Isso é diferente de benchmark noise; a configuração de medição mostrou tempos de execução muito consistentes, como ± 0.01
    • Pequenas mudanças em uma área podiam puxar a medição para um lado
  • Houve até um caso em que, ao mudar o tamanho do generation number, apareceu de forma consistente um overhead negativo de 1.13 ± 0.01
    • Isso foi estranho porque o programa não tinha muitos generation numbers
    • É possível que mudanças na alocação de registradores tenham dominado a diferença de desempenho que viria da mudança semântica
  • Em um programa maior, um pequeno jogo roguelike, o optimizer não conseguiu fundir dois branches idênticos dentro de um if e também deixou passar outras otimizações óbvias
  • Não está claro qual impacto a existência de um inteiro não lido causa, e isso também pode ser um bug do LLVM
  • Isso sugere que talvez seja necessário um pre-optimizer específico para Vale, semelhante ao MIR do Rust
    • O LLVM foi projetado mais pensando em C
    • Se o LLVM interpretar generational references como um padrão de acesso intencional a memória desalocada, pode tratá-lo como undefined behavior

Aplicabilidade e próximos passos

  • Referências geracionais e regiões podem, quando combinadas, criar uma abordagem de segurança de memória muito rápida
  • Os domínios de software em que essa abordagem pode se encaixar bem têm as seguintes características
    • desejam latência mais previsível do que tracing garbage collection
    • desejam melhor desempenho e cache friendliness do que reference counting
    • querem prototipagem e iteração mais fáceis do que com borrow checking
  • Ainda há trabalho restante antes de o Vale poder ser comparado de frente com C ou C++
    • O optimizer do LLVM tem dificuldade para inferir generation e imutabilidade, então é necessário um pre-optimizer específico para Vale
    • O Vale precisa oferecer suporte a inline data em vez da solução temporária atual, que coloca todos os structs no heap
    • Como o benchmark acima não usou structs, a falta de suporte a inline data não afetou esse resultado
    • Os recursos de região ainda estão em fase de protótipo, então é preciso aparar as arestas e reduzir a dívida técnica antes de mesclá-los à branch principal
  • Depois da mesclagem, o plano é fazer a biblioteca padrão usar regiões, para que o código principal do programa do usuário se beneficie disso mesmo sem usar regiões diretamente
  • A medição atual mostra que programas zero-check são possíveis e podem alcançar a velocidade esperada

1 comentários

 
GN⁺ 2023-07-13
Comentários do Hacker News
  • Baixei o Vale para testar, mas tive uma má impressão quando executei o compilador valec pela primeira vez sem argumentos e ele imediatamente imprimiu "(panic)"
    panic é uma expressão muito forte, e acho que deveria ser evitada em situações normais de tratamento de erro. Quando um programa entra em pânico, parece uma situação fora de controle, o que deixa uma sensação ruim
    Em seguida tentei ver a ajuda dos argumentos de linha de comando, mas na prática ela não existia; salvei o exemplo Hello World do site em hello.vl e executei valec hello.vl, e apareceu Unknown subcommand
    Então executei valec build hello.vl, mas depois de Unrecognized input: hello.vl apareceu novamente (panic), e valec help também não ajudou, então acabei desistindo. Não faço ideia de como usar isso

    • Desculpe. Parece que o arquivo de ajuda não está mais sendo exibido corretamente. Se você der cat diretamente no valec-help-build.txt incluído no download, vai encontrar a explicação do que está procurando
      O compilador ainda está bem bruto nas bordas neste momento. De agosto até maio, estive 100% focado em prototipar regions, e o que você está vendo agora é a dívida técnica acumulada durante esse processo. Isso inclui a falta de testes de integração para o sistema de ajuda
      Nos últimos 1~2 meses venho pagando essa dívida, mas ainda não consegui voltar ao nível em que estava no lançamento 0.2. Se precisar de mais ajuda, me avise ou apareça no servidor do Discord, onde há muita gente que pode ajudar
    • Acho que o Vale ainda está essencialmente em fase de P&D. É o tipo de coisa em que se espera que apenas commits específicos de uma branch específica funcionem; ainda é difícil dizer que estamos em um ponto em que qualquer pessoa possa baixar o compilador e construir algo com ele
      Dito isso, o README não deixa isso claro e diz “Try Vale”, então fica ambíguo. Mesmo assim, no momento parece mais próximo de P&D/prova de conceito
    • Isso me parece menos um bug e mais um problema de interface do usuário. Se é algo experimental, acho razoável ter certa tolerância até para bugs de verdade
      Mesmo do ponto de vista de interface ou bugs, a experiência de depurar C++ de 40 anos com um gdb de 35 anos compete tranquilamente com qualquer linguagem experimental. Por exemplo, exibir funcname()::staticvarname é uma interface estranha e falha metade do tempo. Sem nem falar dos sistemas de build de C++
      Se é uma tecnologia experimental, dá para criticar o conceito, mas acho aceitável relevar uma interface meio áspera
    • O GitHub README mostra como usar o compilador
      https://github.com/ValeLang/Vale#building-a-vale-program
    • Para um software ainda em fase alfa, isso é mais ou menos o esperado
  • O fato de ter latência mais previsível do que coleta de lixo por rastreamento, melhor desempenho e melhor afinidade com cache do que contagem de referências, e ser mais fácil para prototipar e iterar do que verificação de empréstimos, já me deixou mais do que curioso — fiquei interessado de verdade
    Até comecei a assinar o feed RSS: https://verdagon.dev/rss.xml

    • Finalmente surgiu uma nova ideia para linguagens compiladas AOT que não termina em “vamos simplesmente deixar bugs de memória acontecerem de vez em quando”
  • O Vale precisa de mais apoiadores
    https://github.com/sponsors/ValeLang
    Espero que, enquanto este post estiver na primeira página, o projeto consiga atingir a meta de US$ 3.000 por mês
    Quero ajudar o Evan a poder trabalhar nisso em tempo integral. Eu também sou apoiador. Uma linguagem rápida, segura e divertida para prototipar merece apoio

    • Tenho curiosidade sobre como a divisão da receita difere entre apoio pelo GitHub e pelo Patreon
  • “Um pré-otimizador específico do Vale, algo parecido com o Cranelift do Rust” provavelmente se refere ao MIR, ou seja, representação intermediária de nível médio. Há um bom post no blog sobre isso: https://blog.rust-lang.org/2016/04/19/MIR.html
    O Cranelift é um backend de compilador focado principalmente em JIT, mas em teoria também poderia substituir o LLVM. O trabalho em um backend alternativo também está em andamento, mas com limitações: https://github.com/bjorn3/rustc_codegen_cranelift

    • Acho que é isso mesmo. O Cranelift seria como um otimizador de Rust para WebAssembly
  • Uma abordagem que permite não se preocupar com gerenciamento de memória na maior parte do código, enquanto dá a opção de otimizar apenas os caminhos quentes com abstrações de custo zero, parece reunir o melhor dos dois mundos
    Principalmente se, por conveniência, o trade-off for só de desempenho e não de segurança

    • Eu só uso C++, mas não me preocupo nem um pouco com gerenciamento de memória
      Propriedade compartilhada é um conceito ruim, então também não uso smart pointers
      Acho que problemas de gerenciamento de memória geralmente são triviais
  • Continuo me perguntando o que significa dizer que isso é seguro no contexto de referências geracionais
    Se entendi direito, isso quer dizer que evita use-after-free e double-free? Se for isso, o programa ainda pode falhar ao acessar a memória quando a geração esperada não corresponde à geração real
    Nesse sentido, parece menos seguro do que contagem de referências, coleta de lixo por rastreamento ou verificação de empréstimos

    • double-free é evitado pela propriedade única do Vale, isto é, propriedade única no sentido de C++, e referências geracionais permitem detectar use-after-free com segurança
      Se você tentar acessar memória liberada por meio de uma referência, isso deve resultar de forma previsível e segura em um erro de segmentação ou falha de asserção. No futuro, esperamos introduzir uma melhoria com remapeamento do espaço de endereços virtuais que pode até eliminar os erros de segmentação
    • É seguro no sentido de que produz erro de segmentação em vez de permitir que um ponteiro pendente leia ou escreva memória arbitrária
      Dito isso, se for um índice geracional, o runtime também deveria conseguir verificar se o acesso é válido antes de tentar o acesso de fato. Não sei se isso é possível no Vale
    • É menos seguro que GC, verificação de empréstimos e contagem de referências. Ainda assim, é mais seguro que malloc/free e tem outras vantagens
    • Também estou curioso. Não entendo como isso evita use-after-free e double-free
      A função check precisa do número de geração da alocação, então ela acessa a alocação. Ou seja, para verificar se a referência pode acessar aquela alocação, primeiro ela precisa acessar essa alocação
      Claro, se a alocação já tiver sido liberada, então o próprio ato de acessar a alocação e seu número de geração já é comportamento indefinido, então isso não funciona
      Parece tão óbvio que não sei se estou deixando passar algo importante, ou se “segurança de memória” aqui está sendo usado em um sentido completamente diferente
    • Se a pergunta é se isso é menos seguro do que o “seguro” definido de forma restrita por você, então sim, provavelmente pode ser
  • Vale não é uma linguagem como V. V recebeu uma análise muito crítica em https://mawfig.github.io/2022/06/18/v-lang-in-2022.html, e eu estava confundindo as duas por causa da semelhança dos nomes
    Estou deixando registrado caso mais alguém tenha cometido o mesmo erro

    • Essa “análise muito crítica” é apenas uma lista de pequenos bugs que foram corrigidos há um ano
      O conteúdo do texto já não é mais relevante, mas ele continua lá e também é o único post daquele blog
    • Essa “análise crítica” parece mais um spam antigo que opositores ou trolls continuam repostando. É uma “análise” da versão alfa da linguagem, na prática um texto agressivo, e não parece ter outro valor além disso
      Agora já é 2023 e V também está em beta (0.4). Além disso, a pessoa que criou aquele texto usou uma conta descartável no GitHub para publicar a análise/ataque, causar polêmica e desaparecer
      O único texto publicado naquele blog também é um ataque ao V, não há outras análises. As partes que tinham conteúdo prático já foram corrigidas[1]
      Se você procurar por mawfig.github, também verá que isso foi repetidamente espalhado no HN e geralmente usado para difamar
      [1]: https://github.com/vlang/v/issues/14803
      [1]: https://github.com/vlang/v/issues/14787
      [1]: https://github.com/vlang/v/issues/14786
  • Parabéns ao Evan por alcançar esse marco. Não tenho experiência com design de linguagens de programação nem com compiladores, mas gosto de ler os textos sobre Vale

    • Sou parecido, mas gostaria que o nome fosse diferente
      Agora temos o Vale do Evan e o Val do Adobe Software Technology Lab, então pesquisar material relacionado provavelmente vai ficar bem difícil
      https://www.val-lang.dev
    • Também não tenho, na maioria dos casos, o conhecimento de base para entender os textos, mas ainda assim acho interessante
    • Também acho os textos excelentes e estou animado com o futuro do Vale
  • “Vale é rápido: Vale é compilado AOT com LLVM, usa tipagem estática, usa a nova técnica de referências geracionais para segurança de memória com velocidade e flexibilidade, e em breve introduzirá verificação de empréstimos por regiões para ficar ainda mais rápido”
    https://vale.dev/

  • Parece que estou ouvindo de fora uma discussão entre duas pessoas que já dura 5 anos
    Alguém pode explicar o que está acontecendo aqui? O texto é difícil demais de acompanhar