2 pontos por GN⁺ 2024-04-15 | 1 comentários | Compartilhar no WhatsApp
  • O WebAssembly teve sucesso ao trazer grandes programas em C++ como o Photoshop para a web, mas sua adoção em apps centrados no DOM foi limitada por causa de um modelo de programação diferente do JavaScript
  • O suporte do navegador a Wasm GC e tipos de referência abre oportunidades para linguagens com memória gerenciada, como Python e Scheme, mas na web o tamanho de transferência vira uma barreira direta à adoção
  • Um programa Wasm simples em Go tem 2 MB, e com imports pode passar de 10 MB; o REPL do Pyodide baixa cerca de 20 MB, o que pesa para apps web comuns
  • O compilador Hoot Scheme tem como alvo Wasm com suporte a GC e reduziu a unidade mínima de compilação “main” para cerca de 70 KB, enquanto unidades auxiliares podem ficar abaixo de 1 KB
  • Um tree shaking eficaz vai além de apagar funções não referenciadas: é um problema de compilador em que análise de fluxo e design da biblioteca padrão precisam trabalhar juntos

Áreas em que o WebAssembly teve resultados na web

  • O WebAssembly não se espalhou de forma tão ampla quanto se esperava no início para a web, mas obteve sucesso limitado em áreas específicas
  • O caso mais representativo é trazer grandes programas em C++, como o Photoshop, para a web
  • O Figma também é citado como exemplo de Wasm de 5 anos atrás, mas hoje já não enfatiza tanto o Wasm
  • Muitas bibliotecas pequenas de NPM compiladas a partir de C++ ou Rust usam Wasm internamente
  • O Blazor pode ser usado em alguns apps corporativos internos, mas talvez seu marketing tenha exagerado um pouco
  • A demo 3D FPS da Unreal Engine foi um experimento baseado numa grande versão de lançamento de mais de 5 anos atrás, e hoje a Unreal 5 não oferece suporte a destino WebAssembly

Por que o Wasm travou em apps centrados no DOM

  • O WebAssembly tem resultados fora da web e pode se tornar mais importante também na plataforma web, mas na web ele pode ser visto como algo que está apenas saindo do vale da desilusão
  • O Wasm é forte em tarefas que o JavaScript não faz bem, ou em trabalhos que exigem implementação compartilhada entre cliente e servidor
  • Em apps centrados no DOM, o Wasm não teve sucesso
    • Ninguém fala em reescrever o frontend do wordpress.com em Wasm
    • O principal modelo de programação da web é o JavaScript, com tipagem dinâmica e memória gerenciada
    • O WebAssembly 1.0 foi projetado em torno de tipagem estática e memória linear
    • Acessar o DOM a partir de Wasm era trabalhoso a ponto de só entusiastas muito dedicados toparem isso
  • Linguagens como C# precisam distribuir também um coletor de lixo, e isso virou obstáculo para adoção de Wasm por linguagens que não são C/Rust

O problema do tamanho de transferência que continua mesmo após o Wasm GC

  • Nos próximos meses, os navegadores devem oferecer suporte a tipos de referência e coleta de lixo
    • Chrome e Firefox já oferecem Wasm GC
    • O Safari também não deve demorar, graças ao trabalho de Asumu Takikawa
  • O Wasm GC é uma mudança que leva mais linguagens a atualizar seus toolchains para oferecer suporte a WebAssembly
  • Para o Wasm na web dar certo, o compilador precisa gerar código pequeno
    • É vantajoso quando o toolchain da linguagem consegue produzir arquivos Wasm úteis na faixa de alguns KB para transferência
    • Caso contrário, ele precisa depender de hype ou de um público cativo e fica preso num equilíbrio instável até surgir outra solução
  • No ecossistema JavaScript, já existe uma grande indústria de ferramentas para reduzir tamanho de entrega e inchaço
    • Bundlers como o esbuild juntam vários módulos JS em um único arquivo
    • Eles tentam incluir apenas as funções e tipos de dados realmente usados
    • Também aplicam estratégias de redução de tamanho, como minification, encurtando nomes

A armadilha do nome tree shaking

  • Tree shaking traz uma metáfora visual: manter só o código necessário numa página e deixar o resto cair
  • Nessa metáfora, módulos viram galhos e definições viram folhas, mas numa árvore real balançar o tronco não diz quais galhos são necessários e quais não são
  • O próprio nome leva a pensar em remover código desnecessário, mas do ponto de vista algorítmico faz mais sentido enxergar isso como encontrar um ponto fixo que preserve apenas o código necessário
  • Mesmo assim, tree shaking é um nome forte e continua sendo usado apesar da sua imprecisão horticultural e algorítmica

A barreira de tamanho causada por runtimes pesados

  • Em linguagens com runtimes pesados, tree shaking máximo não era uma prioridade tão alta
  • No suporte a WebAssembly do Go, até o programa mais simples tem 2 MB segundo a wiki do golang
    • Ao adicionar imports, ele pode passar de 10 MB
  • O exemplo de REPL do Pyodide, um port de Python para WebAssembly, baixa cerca de 20 MB de dados
  • Esses tamanhos podem servir para demos técnicas ou aplicações muito ricas, mas são difíceis de aceitar como uma opção comum de desenvolvimento web

Toolchains alternativos e implementações ajustadas à plataforma

  • O suporte embutido a Wasm do Go e o Pyodide derivam de toolchains upstream, e no servidor o tamanho do binário pode não importar tanto
  • Ao mirar dispositivos pequenos, aparecem implementações separadas
  • O backend Wasm do TinyGo aparentemente consegue cair para menos de 1 KB
  • Esses toolchains alternativos muitas vezes vêm com limitações ou peculiaridades
  • Um programa Python com destino Wasm executado em ambiente DOM inevitavelmente será diferente de um programa Python “nativo”
  • Os autores do toolchain tentam oferecer a mesma linguagem, mas a implementação da biblioteca padrão pode ser diferente
  • Desenvolvedores de ClojureScript provavelmente também gostariam de eliminar, se possível, a documentação de diferenças em relação ao Clojure, e se o Wasm se tornar um destino prático para ClojureScript essa possibilidade pode surgir

Como o Hoot Scheme faz tree shaking

  • Após o suporte a GC, o Wasm pode permitir pensar em programação DOM com linguagens como Python, mas para uso popular os módulos precisam ser pequenos
  • O compilador Hoot Scheme tem como alvo Wasm com GC
    • Hoje a unidade mínima de compilação “main” tem cerca de 70 KB
    • O objetivo é reduzir ainda mais
    • Unidades auxiliares de compilação que importam recursos de runtime, como tratadores de exceção, a partir do módulo main podem ficar abaixo de 1 KB
  • O compilador Hoot anexa um prelude antes do código do usuário
  • O tree shaking acontece em várias etapas
    • A avaliação parcial pode avaliar apenas os efeitos de bindings não usados e depois removê-los
    • fixing letrec faz algo semelhante
    • O CPS percorre o programa repetidamente, seguindo apenas funções, valores e arestas de fluxo de controle que são referenciados
    • Um passo explícito de eliminação de código morto remove atribuições sem efeito e sem uso que podem surgir após outras otimizações
    • Definições da biblioteca padrão escritas em WebAssembly próximo do raw só entram no binário final quando necessário

O que é fácil remover e o que é difícil

  • Definições de procedimentos, como funções ou closures, são relativamente fáceis de lidar
    • Basta incluir apenas as funções que o código referencia
    • Em linguagens como Scheme, só isso já produz um efeito considerável
  • Três dificuldades aparecem logo de cara
  • Modelo de avaliação de letrec*

    • O escopo das definições do prelude é recursivo, mas tem ordem
    • O valor de um binding pode chamar ou referenciar valores definidos antes, e também pode capturar valores que só serão definidos depois
    • Se for necessário referenciar, para avaliar um binding, um valor que só é definido mais tarde, isso é erro
    • Em procedimentos isso em geral não é um problema, mas em definições que não são procedimentos o compilador pode não conseguir provar a propriedade de que “só referencia bindings anteriores”
    • Nesse caso, o algoritmo fixing letrec reloaded pode deixar bindings com set!, e removê-los exige um passo cuidadoso de DCE
  • vtable de tipos de registro

    • Algumas definições que não são procedimentos são tipos de registro
    • Tipos de registro têm uma vtable com itens como a forma de imprimir o registro ou de testar instâncias
    • Mesmo que callbacks da vtable não sejam realmente usados, eles podem manter muito código vivo
  • Funções de saída polimórficas

    • Funções polimórficas como display ampliam muito o escopo do código necessário
    • Chamar display para imprimir uma string traz junto toda a infraestrutura de I/O com buffer
    • Como display pode imprimir qualquer coisa, ele também puxa código para vários casos, como bitvector, pair etc.
    • Chamar write-string, que serve só para strings, evita o código geral de impressão de dados, mas ainda assim inclui infraestrutura genérica de I/O com buffer, como ports

Tree shaking ótimo é um problema de análise de fluxo

  • O tree shaking ideal é, no fim, um problema de análise de fluxo
  • Se um programa nunca tiver bitvector, então o código dentro de display que trata bitvector pode ser código morto
  • Para saber isso, é preciso saber com que tipos de argumento display é chamado, e isso exige análise de fluxo de alto nível
  • Em Python, o problema fica mais difícil
    • Dispatch orientado a objetos é programação de ordem superior, então o significado de foo.bar depende do que é foo
    • A busca em Python é mais dinâmica do que em Scheme, e métodos como __getattr__ podem aparecer por toda parte
    • Na prática, talvez a análise de fluxo consiga excluir essas buscas dinâmicas
    • O alvo do tree shaking em Python não é um conjunto grande de itens com bindings léxicos, e sim um conjunto complexo de módulos
    • Isso se parece com JavaScript, mas Python não tem um ecossistema consolidado de bundlers com tree shaking

Condições para toolchains de linguagens Wasm na web

  • O Wasm GC pode tornar viável programar o DOM com linguagens que não sejam JavaScript
  • Para isso chegar ao uso popular, os módulos Wasm gerados precisam ser pequenos
  • Cada toolchain de linguagem exige investimento considerável
  • Esse investimento muitas vezes aparece na forma de toolchains alternativos, incluindo algoritmos experimentais de tree shaking
  • Bibliotecas padrão alternativas precisam ser projetadas para permitir que tree shakers funcionem melhor

1 comentários

 
GN⁺ 2024-04-15
Comentários do Hacker News
  • Mantive o blob Wasm do openEtG (motor do jogo de cartas) com menos de 400 KB, ao mesmo tempo em que transferi muita lógica para Wasm, como a geração de texto das cartas, e tudo foi escrito em Rust
    Para reduzir o tamanho, foi preciso gerenciar coisas como usar aritmética de ponto fixo em vez de ponto flutuante, trocar hash maps por vetores, evitar strings, usar um alocador pequeno como talc e reduzir dependências
    Estou usando apenas rand e fxhash, mas parece que também daria para remover rand; fxhash é usado só para verificar dessincronização via hash do estado do jogo
    Também reduzi os tipos de instâncias genéricas para não acabar trazendo tipos extras como Box<[i16]>, já que Vec já existe, e remover ponto flutuante e hash maps também ajudou a reduzir a diversidade de tipos
    Os algoritmos também foram projetados levando em conta o tamanho; por exemplo, uma tabela de consulta com bit packing codifica a mecânica adrenaline, em que criaturas com menor ataque atacam mais vezes
    Comparei o custo de armazenar valores não comprimidos com o custo da lógica de decodificação, e a avaliação da IA usa precisão fixa de 6 bits, porque em WebAssembly 64 é codificado com mais eficiência do que 128
    O mecanismo de targeting também costumava ser uma AST em que cada predicado era um enum e AND/OR eram fatias de expressão, mas agora a expressão é codificada em um inteiro de 32 bits em notação polonesa, com AND/OR ocupando 2 bits e os predicados 6 bits
    Aqui, a notação polonesa foi melhor do que a notação polonesa reversa, porque permitia avaliação com curto-circuito de AND/OR

    • É interessante que a aritmética de ponto fixo tenha economizado bastante espaço, mesmo com tipos de ponto flutuante disponíveis nativamente em Wasm
      Estou pensando se ponto fixo poderia ajudar em problemas no trabalho em que conhecemos o requisito máximo de resolução — por exemplo, quando não é necessária precisão de posição abaixo de milímetros —, então gostaria de ouvir mais sobre questões relacionadas
    • Outra coisa que ajudou foi usar o wasm-opt do binaryen
      Parece reduzir de forma consistente o tamanho do Wasm em cerca de 20% a 30%: https://github.com/WebAssembly/binaryen
      Ao servir bundles Wasm no navegador, também é bom usar compressão Brotli e configurar o servidor web para usar arquivos comprimidos com Brotli
      No nginx isso pode ser feito com uma mudança de uma linha; Brotli reduz o tamanho dos bundles Wasm em cerca de 3 vezes e é muito melhor que gzip
    • Criei uma biblioteca de aprendizado por reforço profundo em C++, a RLtools, e também fiz exemplos em Wasm (https://rl.tools); mesmo sem me preocupar nada com o tamanho do binário, o total ficou em torno de 200 a 300 KB
      Esse tamanho inclui feedforward/backpropagation de deep learning, algoritmos de aprendizado por reforço e até simulações de dinâmica
      Mesmo hoje não chega a ser pesado, mas estou curioso para ver quanto mais dá para reduzir, então quero tentar diminuir isso em breve
    • Eu achava que JavaScript usava double para todos os números; fico curioso se Wasm é completamente diferente nesse ponto
      Quero entender como faz sentido dizer que evitar ponto flutuante e usar aritmética de ponto fixo economiza espaço
    • Gostaria de ver números mostrando quanto espaço foi economizado de fato
      Especialmente algo como o item 6, usar Vec como se fosse Box, não parece que levaria a uma grande economia
  • O nome tree shaking parece um termo bastante inadequado
    O compilador Virgil chama isso de “análise de alcançabilidade” e a incorpora ao modelo de compilação
    O compilador faz o parsing do programa e do código de bibliotecas, verifica tipos e executa o código de inicialização, mas, depois disso, percorre a partir do ponto de entrada principal e só analisa e inclui no binário final o código que é alcançável
    Ele também gera bem programas sem sistema de runtime, com apenas uma única função main; o sistema de runtime só é necessário para stack traces e coleta de lixo, então pode ser omitido se desejado

    • Treeshaker como nome de ferramenta de distribuição de aplicações possivelmente vem do Lisp
      O primeiro caso que encontrei foi a ferramenta Treeshaker do Lucid Common Lisp 4.1, de 1992, que era uma implementação comercial de Common Lisp para UNIX
      O Lucid CL tinha o conceito de imagem, um dump de memória salvo do heap Lisp em execução, e uma aplicação era composta por uma imagem e um runtime
      Como a imagem normalmente incluía quase todo o código e os dados na memória, surgiu o desejo de criar imagens menores para distribuição, e o Treeshaker removia código e dados considerados “não usados” antes de salvar a imagem
      Era algo como podar conexões no grafo de dados e código Lisp alcançáveis, e então o GC ou um código especial coletava o lixo para reduzir a memória antes de despejar uma imagem menor
      Portanto, o Treeshaker não era uma ferramenta de compilador, mas uma ferramenta para remover código e dados não usados do heap Lisp
      Como a imagem Lisp básica incluía até o compilador, o interpretador e a implementação do REPL, se você interrompesse o programa em execução e entrasse no REPL, ainda poderia usar todo o código dentro do heap restaurado a partir da imagem
      Por isso fazia sentido remover até o compilador ou o REPL
    • Em geral, o termo correto é eliminação de código morto (dead-code elimination): https://en.wikipedia.org/wiki/Dead-code_elimination
      A análise de alcançabilidade é frequentemente usada para decidir qual código pode ser removido, mas a análise em si não remove código; a remoção é uma etapa posterior
      “Tree shaking” normalmente sugere remoção no nível de funções, enquanto a eliminação de código morto também pode ocorrer em níveis muito mais granulares, como remover ramificações de expressões condicionais, e pode se basear em várias análises estáticas
    • Há muitos nomes mal atribuídos em software, mas, pessoalmente, acho tree shaking um ótimo nome
      Quando vi pela primeira vez, entendi o significado de imediato, sem pesquisar mais
      É sacudir uma árvore para fazer cair o que está preso de forma frouxa; aqui, ficou claro que pacotes não usados são “sacudidos para fora” da árvore
    • Nunca pensei que tree shaking se referisse a uma árvore vegetal; imaginei uma árvore como estrutura de dados
      Se você imaginar um diagrama de código-fonte como um objeto físico, ao sacudi-lo, as partes que não são alcançáveis a partir da raiz se soltam e caem
      Não é muito diferente de análise de alcançabilidade; uma das expressões apenas evoca mais raciocínio espacial
    • Há muito código, e ele é uma árvore
      Parte do código não está conectada ao tronco que é o ponto de entrada
      Tree shaking remove as partes desconectadas, ou seja, folhas soltas e galhos mortos
  • Se a toolchain de compilação de uma linguagem conseguir produzir Wasm útil com apenas alguns KB em termos de transferência pela rede, novas possibilidades se abrem
    Binários minúsculos abrirão novos casos de uso para Wasm, e WasmGC certamente ajuda
    Java e Kotlin também já conseguem resultados bem bons hoje, na faixa de 2–3 KB: https://developer.chrome.com/blog/wasmgc, https://twitter.com/bashorov/status/1661377260274720770
    Mas é preciso cuidado, porque dependendo da API usada, muito código pode vir junto
    Ainda assim, graças ao WasmGC, essas linguagens já estão bem melhores que C++ e Rust em tamanho de código, pois não precisam incluir vários KB de código de gerenciamento de memória no bundle

    • Fico curioso se WasmGC também ajudaria Rust
      Ao lidar com objetos JavaScript, pode ajudar, e talvez possa ser usado como um alocador de memória alternativo menos eficiente
      Mesmo assim, o alocador padrão do Rust em Wasm provavelmente é adequado na maioria dos casos
      Se você ativar otimizações de tamanho, usar wasm-opt e comprimir com Brotli, dá para colocar uma quantidade enorme de código em menos de 100 KB de download
      É um erro comparar diretamente o custo de 100 KB de Wasm com 100 KB de JavaScript empacotado, porque JavaScript é várias vezes mais lento para fazer parsing e inicializar
      O tempo de download é um custo real, mas o tempo até a primeira renderização é muito melhor com 100 KB de Wasm do que com 100 KB de JavaScript
      Ainda assim, quanto menor, melhor, e é empolgante ver Java, Kotlin, C#, Python, Go etc. se tornando linguagens práticas para aplicações web
      Também fico curioso para ver qual será o tamanho de aplicações reais
      A maior diferença provavelmente virá do design dos frameworks, e a comparação de DOM virtual inevitavelmente será sempre mais complexa e mais lenta do que bibliotecas de componentes reativos como Svelte, SolidJS e Leptos, do Rust
      Quando o WasmGC for compatível em todos os lugares, a escolha do framework web provavelmente terá um impacto muito maior no desempenho do que a linguagem
  • Tenho dúvidas se a afirmação de que “Wasm torna concebível programar o DOM em linguagens que não sejam JavaScript” é realmente correta
    Pelo que sei, para manipular o DOM em linguagens como Rust, no fim ainda é preciso ter bindings que serializam chamadas a serem executadas do lado do JavaScript
    Na forma atual do Wasm, acho que ele ainda está preso ao JavaScript

    • É importante incluir a condição anterior: “com GC
      Em teoria, agora seria possível importar funções do DOM no runtime e chamá-las com referências a objetos DOM, contornando o JavaScript e chamando o runtime diretamente
      Não tenho certeza se isso é realmente possível na prática, mas o GC ao menos fornece o mecanismo pré-requisito para chegar a esse ponto
    • Recentemente experimentei o Leptos para Rust; é um framework muito rápido para criar front-ends web com Wasm e, sinceramente, parece bom
      Basicamente, é parecido com o SolidJS
      #[component] fn App() -> impl IntoView { let (count, set_count) = create_signal(0); view! { "Click me: "{move || count()} } }
      Em comparação com JavaScript, há um overhead no tamanho do Wasm, mas não é grave
      Depois de wasm-opt e compressão Brotli, o bundle Wasm desse app de contador ficou com 37 KB, numa faixa parecida com React, e depois de executado é muito mais rápido
      Não testei manipulação direta do DOM, mas parece bom para componentes comuns
    • Pelo que entendi, uma das intenções do WASI é fornecer uma API que vá diretamente ao DOM
      Por exemplo, algo como usar um DOM implementado em Rust a partir de um módulo Wasm escrito em Rust, sem execução de JavaScript
      Só que é complicado porque partes da API do DOM foram especificadas de acordo com a semântica do JavaScript; por isso, parecem estar avançando primeiro em coisas com menos legado de JavaScript, como requisições HTTP, sockets TCP e acesso ao sistema de arquivos
  • O termo “eliminação de código morto” existe há muito tempo; fico curioso por que surgiu a expressão tree shaking

    • “Eliminação de código morto” normalmente se refere a uma otimização de compilador em pequena escala que remove trechos de código dentro de uma função que jamais serão alcançados
      “Tree shaking” se refere a uma análise de programa inteiro que descarta módulos e funções inteiros que não são chamados
      Conceitualmente é a mesma coisa, mas autores de compiladores em geral precisam implementá-las separadamente, então é útil haver dois nomes
    • O fato de uma palavra sobreviver e virar mainstream não tem muita relação com ela ser antiga
      Tree shaking é fácil de imaginar, mais fácil de dizer que “eliminação de código morto” e mais acessível, então parece ter se tornado um termo mais popular
      Ao pesquisar qual termo veio primeiro, encontrei o uso mais antigo de “dead-code elimination” em um artigo de 1973: https://research-repository.st-andrews.ac.uk/bitstream/handle/10023/22636/NicholasAlexandrakisMScThesis1973_original_C.pdf?sequence=1
      No Google Scholar, não encontrei usos de “tree shaking” ou “tree shaker” na área de computação; a maioria era sobre árvores, como cítricos
      O que parece ser a discussão mais antiga foi um post no comp.lang.lisp: https://groups.google.com/forum/#!topic/comp.lang.lisp/pspFr1XByZk
    • Se de fato veio do contexto de Lisp, deve ser porque a remoção de dados e metadados desnecessários era tratada com a mesma importância que a remoção de código executável
    • A expressão eliminação de código morto é muito melhor
    • É uma expressão mais vívida e soa mais coloquial do que “eliminação de código morto”, então não é difícil entender por que se espalhou
  • Acho que a metáfora de tree shaking talvez venha de alguns métodos de colheita de árvores frutíferas
    Ao sacudir a árvore, os frutos maduros caem
    Só que, na colheita de frutas, o que cai é o desejado; ao armazenar uma imagem serializada, o que cai é descartado, então não é uma metáfora tão boa assim

    • Normalmente, o que se colhe sacudindo são nozes e castanhas
      Frutas são mais delicadas e, se caem de uma altura grande demais, amassam; por isso, em geral são colhidas à mão
      O processo de sacudir é bem brusco, mas não prejudica a árvore
  • Minha filosofia é otimizar bastante tamanho e desempenho em JavaScript por meio de algoritmos e simplificação de design, para abrir espaço no tamanho do bundle e folga de CPU para código que exige computação bruta
    No projeto ClubCompy, estou usando Wasm para implementar um sistema de arquivos FAT sobre armazenamento local, e isso acabou sendo muito caro computacionalmente
    Também pretendo usar Wasm mais adiante este ano, quando eu recolocar a detecção de colisão de sprites com precisão por pixel
    A primeira implementação era JavaScript puro e, quando os 256 sprites na tela colidiam entre si, o framerate caía para menos de 1 fps
    Acho que será possível processar isso praticamente de graça em uma worker thread, sem impacto no desempenho

    • Detecção de colisão por pixel parece boa à primeira vista, mas pode ser desagradável na prática, ao jogar
      Há um motivo para a maioria dos jogos 2D usar caixas de colisão retangulares: é mais fácil para o jogador prever se haverá colisão
      Com colisão por pixel, o mesmo movimento pode ou não colidir dependendo da fase do ciclo de animação
      Se um movimento que antes sempre funcionava falha porque o timing da animação coincidiu de um jeito ruim, a sensação é ruim
      Além disso, colisão por pixel nem sempre é realista: pequenos detalhes de um sprite podem representar elementos como tecido ou cabelo, que na prática não causariam uma colisão rígida
      Também é comum sprites se moverem vários pixels por frame, o que aumenta a chance de colisões entre pixels individuais simplesmente atravessarem uns aos outros
      Uma detecção de colisão simples e previsível costuma ser a melhor opção
    • Se com 256 colisões fica em 1 fps, o gargalo é o algoritmo, não a linguagem de programação
      1 fps é um framerate que deveria aparecer com algo como 20 mil colisões
      Se for por pixel, quando há memória suficiente, uma abordagem simples é renderizar um canvas offscreen do campo inteiro, desenhar cada sprite como um stencil de cor diferente e então inspecionar isso
      É tempo linear e nem exige particionamento separado
      Porém, medidas anti-fingerprinting podem transformar os bits menos significativos dos dados do canvas em ruído, então talvez seja preciso usar os bits mais significativos
  • Este texto está dizendo algo correto. Wasm tem um problema de tamanho de código
    No navegador isso é um problema porque todo o código precisa ser baixado antes de o site iniciar; em arquiteturas serverless também é um problema, porque o código é carregado sob demanda do cold storage para um servidor específico enquanto o cliente espera
    Tree shaking pode ajudar, mas provavelmente ficará só como uma otimização incremental
    Fundamentalmente, a razão pela qual programas Wasm ficam inchados é que precisam trazer junto o runtime da própria linguagem e a biblioteca padrão inteira
    Em contraste, no JavaScript, a implementação e a biblioteca básica são fornecidas pelo navegador
    É fácil pensar que, ainda assim, o navegador não poderia carregar previamente todos os runtimes de linguagem, mas outra abordagem deve ser considerada: bibliotecas compartilhadas e linkedição dinâmica
    WebAssembly oferece suporte a linkedição dinâmica, e vários módulos Wasm podem ser carregados ao mesmo tempo e chamar uns aos outros
    Mas muitas toolchains de Wasm não querem dar suporte a isso, e foram projetadas para linkar estaticamente o programa inteiro e o runtime da linguagem em um único módulo enorme
    Pyodide (CPPython em Wasm) é um contraexemplo: hoje ele é projetado com linkedição dinâmica em mente
    Foi justamente graças a isso que o Cloudflare Workers pôde adicionar recentemente Python como suporte de primeira classe: https://blog.cloudflare.com/python-workers
    Do ponto de vista da liderança técnica de toda a plataforma Workers, todos os Workers executados na mesma máquina compartilham um único runtime Pyodide compilado, então não é preciso carregá-lo separadamente para cada Worker
    Se a linkedição dinâmica fosse suportada de forma mais ampla, poderíamos imaginar uma estrutura em que o navegador pré-carregasse runtimes de linguagens populares e até bibliotecas populares, e todas as páginas web que precisassem desse runtime compartilhassem a mesma cópia de código somente leitura
    Esses runtimes ainda seriam executados dentro de uma sandbox, então o navegador não precisaria confiar neles; bastaria fornecê-los
    Isso permitiria criar navegadores com suporte “embutido” a linguagens além de JavaScript, sem que os mantenedores do navegador precisassem verificar ou se preocupar completamente com a implementação da linguagem

    • A ideia de o navegador pré-carregar vários runtimes e bibliotecas populares para que páginas web os compartilhem soa parecida com a ideia antiga de fornecer frameworks JavaScript comuns por meio de um CDN compartilhado, de modo que eles provavelmente já estariam no cache da maioria dos navegadores, dispensando download e tornando o tamanho do framework pouco importante
      Não conheço bem o assunto, mas entendo que esse modelo não funcionou bem
      Havia versões demais das bibliotecas, de modo que versões individuais não eram realmente usadas de forma ampla, e depois, por preocupações de privacidade, os navegadores passaram a separar caches por site ou por origem
      Talvez a proposta nem tenha sido pensada em termos de cache, mas este é um problema espinhoso mais próximo de um problema social do que de um problema técnico
      Colocar mais runtimes de linguagem como suporte básico do navegador aumenta a barreira de entrada para novos navegadores, e também não é possível oferecer suporte a todas as bibliotecas e runtimes que as pessoas querem
      Se cada um trouxer o seu e confiar no cache, fica a questão de como evitar os problemas já vividos com cache de bibliotecas JavaScript
    • Mesmo um suporte limitado a linkedição dinâmica parece que teria um efeito bem grande
      Por exemplo, uma biblioteca compartilhada de Go poderia incluir o runtime e as partes centrais da biblioteca padrão usadas por muitos programas
      Mesmo sem suporte a bibliotecas dinâmicas dentro do app, seria possível reduzir o tamanho de todos os programas Go, e o runtime da linguagem não precisaria fazer otimização de espaço tão agressiva
      Como ele já está carregado e, se algum programa usar sequer uma função, não é espaço desperdiçado
      Isso mudaria o modelo de custo de otimização de tamanho para programas dessa linguagem
      Funções da biblioteca padrão incluídas se tornam, na prática, gratuitas assim que se usa a linguagem, então basta usá-las
      Porém, o problema se repete em bibliotecas e frameworks comumente usados
      Ao executar na Cloudflare, provavelmente você também gostaria de compartilhar a biblioteca padrão da Cloudflare para Go
      O problema é que linguagens não evoluem no mesmo ritmo que seus runtimes
      O suporte a múltiplas versões da linguagem pode ficar limitado, ou bibliotecas compartilhadas podem se acumular com o tempo, reduzindo o efeito de compartilhamento entre apps
      JavaScript tem um modelo de versão baseado em “não há escolha”, exigindo forte compatibilidade retroativa e, às vezes, polyfills
      Isso pode ser menos adequado para outras linguagens
      Se o runtime realmente quiser reduzir espaço, basta limitar a diversidade de plugins
      Apesar das reclamações, o modelo de “é preciso usar JavaScript” funcionou bastante bem nos navegadores
      Talvez linguagens baseadas em WebAssembly não precisem ser tão diversas, e a diversidade tem custos, como em uma situação de Torre de Babel
    • Fico curioso se seria possível usar tree shaking baseado em perfil para criar um módulo pequeno contendo apenas o código de que a aplicação precisa, e carregar recursos menos usados sob demanda por linkedição dinâmica
      Fazer tree shaking com base em informações reais de produção parece permitir cortar muito código morto ou quase morto sem implementar algoritmos sofisticados de análise estática
    • Uma estrutura em que o navegador baixa previamente runtimes populares corre o risco de acabar com centenas de versões de runtimes baixadas no navegador, cada binário usado por apenas um site, enchendo o cache
    • Concordo no geral, mas vale apontar que, no Wasm, para fazer coisas que não sejam computação pura, você acaba usando bastante o JavaScript embutido no navegador
      Especialmente em contextos como o Hoot, coisas como appendChild são funções externas chamadas de dentro de Scheme: https://spritely.institute/news/building-interactive-web-pages-with-guile-hoot.html
      Em teoria, em qualquer ambiente Wasm, muitas partes da biblioteca padrão de JavaScript poderiam ser usadas dessa forma
  • Zig é perfeito para esse tipo de uso
    Pessoalmente, acho que, se o arquivo Wasm tiver menos de 100 KB, isso não é um fator importante; se passar de MB, passa a ser
    GC embutido é importante para alguns apps, mas não para todos, e o melhor é criar apps web sem GC
    O fator mais importante para o sucesso de apps que usam Wasm continua sendo a vantagem de desempenho

  • Estou rodando um app Blazor no Cloudflare Pages e, embora o download seja rápido e o desempenho seja bom, o tempo de carregamento é horrível
    Acho que não dá para resolver com .NET, e o problema central parece ser que linguagens orientadas a objetos, por design, acabam deixando tudo interligado
    Além disso, é difícil competir com o volume de dinheiro investido em JavaScript, e JavaScript tem copiar/colar quase como um recurso da linguagem, então parece um cheat code
    Em terceiro lugar, mesmo no Blazor você ainda precisa de JavaScript e de competência nessa área, e vejo isso como o principal problema

    • Isso não é um problema de orientação a objetos
      O problema é que as linguagens daquela época dependiam de reflexão em muitos casos de uso
      Há pessoas trabalhando duro para eliminar esses casos de uso em grande parte do .NET e marcar o código como seguro para remoção quando não utilizado
      Se houver a possibilidade de um método ser chamado por reflexão, fica difícil saber o que pode ser removido com segurança
      Mesmo que pareça que não há nenhum lugar chamando Foo.Bar(), e se alguém fizer Reflection.getClass(someClass).runMethod(someVar) e essas variáveis estiverem definidas como "Foo" e "Bar"?
      Por exemplo, Dart não permite reflexão em apps compilados antecipadamente, o que permite remover código não utilizado com segurança: https://docs.flutter.dev/resources/faq#does-flutter-come-with-a-reflection-mirrors-system
      Dart é uma linguagem orientada a objetos, mas evita geração de código em tempo de execução e reflexão em tempo de execução, optando por geração de código em tempo de compilação
      O .NET também está indo nessa direção, mas isso não acontece da noite para o dia
      Porém, como outros disseram, linguagens que não são JavaScript também têm o problema de precisar enviar junto as partes da biblioteca padrão, incluídas no runtime JavaScript do navegador, que elas usam
    • O Blazor não se destaca nesse aspecto
      O modelo de empacotamento atual não aproveita bem a capacidade de trimming do .NET, por causa das limitações de empacotar Wasm sobre Mono
      Para ver o quanto ele realmente consegue reduzir, é melhor compilar uma aplicação comum com AOT; aí sai um binário pequeno
      O suporte experimental ao alvo NativeAOT-LLVM Wasm em dotnet/runtimelab oferece bundles muito menores e desempenho muito melhor, mas ainda está em dotnet/runtimelab, não em dotnet/runtime, então não sei quando ficará disponível