2 points par GN⁺ 2024-04-15 | 1 commentaires | Partager sur WhatsApp
  • WebAssembly a réussi à amener sur le Web de gros programmes C++ comme Photoshop, mais son adoption dans les applications centrées sur le DOM est restée limitée à cause d’un modèle de programmation différent de JavaScript
  • La prise en charge par les navigateurs de Wasm GC et des types référence ouvre des perspectives aux langages à mémoire managée comme Python et Scheme, mais sur le Web, la taille de transfert devient vite un obstacle à l’adoption
  • Un programme Wasm très simple en Go pèse 2 Mo, peut dépasser 10 Mo avec l’ajout d’imports, et le REPL Pyodide télécharge environ 20 Mo, ce qui est lourd pour une application Web ordinaire
  • Le compilateur Hoot Scheme cible Wasm avec prise en charge du GC, a réduit l’unité de compilation minimale « main » à environ 70 Ko, et les unités de compilation auxiliaires peuvent même descendre sous 1 Ko
  • Un tree shaking efficace ne se limite pas à supprimer les fonctions non référencées : c’est un problème de compilateur où l’analyse de flux et la conception de la bibliothèque standard doivent aller de pair

Les domaines où WebAssembly a porté ses fruits sur le Web

  • WebAssembly ne s’est pas diffusé aussi largement que les attentes initiales sur le Web le laissaient penser, mais il a connu un succès limité dans certains domaines
  • L’exemple emblématique est l’arrivée sur le Web de gros programmes C++, comme Photoshop
  • Figma est aussi cité comme un cas d’usage de Wasm d’il y a cinq ans, mais aujourd’hui l’entreprise ne met plus beaucoup Wasm en avant
  • De petites bibliothèques NPM compilées depuis C++ ou Rust utilisent souvent Wasm en interne
  • Blazor peut être utilisé dans certaines applications d’entreprise internes, même si son marketing a peut-être été exagéré
  • La démo de FPS 3D d’Unreal Engine était une expérimentation fondée sur une version majeure antérieure d’il y a cinq ans, et Unreal 5 ne prend actuellement pas en charge la cible WebAssembly

Pourquoi Wasm bloque dans les applications centrées sur le DOM

  • WebAssembly obtient des résultats hors du Web et pourrait gagner en importance sur la plateforme Web, mais sur le Web on peut considérer qu’il vient tout juste de sortir du creux de la désillusion
  • Wasm est pertinent pour les tâches que JavaScript fait mal, ou lorsqu’il faut une implémentation partagée entre client et serveur
  • Wasm n’a pas percé dans les applications centrées sur le DOM
    • Personne ne parle de réécrire le frontend de wordpress.com en Wasm
    • Le principal modèle de programmation du Web est JavaScript, avec typage dynamique et mémoire managée
    • WebAssembly 1.0 a été conçu autour du typage statique et de la mémoire linéaire
    • Accéder au DOM depuis Wasm était assez fastidieux pour ne convenir qu’aux partisans les plus enthousiastes de Wasm
  • Les langages comme C# doivent embarquer un ramasse-miettes, ce qui a freiné l’adoption de Wasm pour les langages autres que C/Rust

Le problème de taille de transfert qui subsiste après Wasm GC

  • Les navigateurs doivent proposer dans les prochains mois la prise en charge des types référence et du ramasse-miettes
    • Chrome et Firefox fournissent déjà Wasm GC
    • Safari ne semble plus très loin non plus, grâce au travail d’Asumu Takikawa
  • Wasm GC est un changement qui pousse davantage de langages à mettre à jour leurs toolchains pour prendre en charge WebAssembly
  • Pour que Wasm réussisse sur le Web, les compilateurs doivent produire du code compact
    • Si une toolchain de langage peut produire des fichiers Wasm utiles de quelques Ko en taille de transfert, c’est un avantage
    • Sinon, il faut compter sur des attentes exagérées ou sur une base d’utilisateurs captive, et rester dans un équilibre instable jusqu’à trouver la prochaine solution
  • Dans l’écosystème JavaScript, toute une industrie d’outils existe déjà pour réduire la taille livrée et l’embonpoint
    • Les bundlers comme esbuild regroupent plusieurs modules JS en un seul fichier
    • Ils cherchent à n’inclure que les fonctions et types de données utilisés
    • Ils appliquent aussi des stratégies de réduction de taille comme la minification des noms

Le piège du nom tree shaking

  • Le tree shaking repose sur une métaphore visuelle : ne garder que le code nécessaire à une page donnée et faire tomber le reste
  • Dans cette métaphore, on imagine les modules comme des branches et les définitions comme des feuilles, mais dans un vrai arbre, le fait de secouer le tronc ne vous dit pas quelles branches sont nécessaires ou non
  • Le nom incite à penser en termes de suppression du code inutile, mais du point de vue algorithmique, il est plus pertinent de chercher un point fixe qui ne conserve que le code nécessaire
  • Malgré cela, « tree shaking » est un nom marquant, et il continue d’être utilisé malgré son inexactitude horticole et algorithmique

La barrière de taille créée par les runtimes lourds

  • Dans les langages à runtime lourd, un tree shaking maximal n’a pas été une grande priorité
  • Dans la prise en charge WebAssembly de Go, même le programme le plus simple pèse 2 Mo selon le wiki golang
    • L’ajout d’imports peut le faire dépasser 10 Mo
  • L’exemple de REPL de Pyodide, un port WebAssembly de Python, télécharge environ 20 Mo de données
  • De telles tailles peuvent convenir à des démonstrations techniques ou à des applications très riches, mais rendent difficile d’en faire une option courante de développement Web

Toolchains alternatives et implémentations adaptées aux plateformes

  • La prise en charge Wasm intégrée à Go et Pyodide sont dérivées des toolchains upstream, et sur serveur la taille des binaires n’est pas forcément cruciale
  • Quand on cible de petits appareils, des implémentations séparées apparaissent
  • Le backend Wasm de TinyGo semble pouvoir descendre sous 1 Ko
  • Ces toolchains alternatives s’accompagnent souvent de contraintes ou de particularités
  • Les programmes Python ciblant Wasm et exécutés dans un environnement DOM ne peuvent que différer des programmes Python « natifs »
  • Les auteurs de toolchains essaient de fournir le même langage, mais l’implémentation de la bibliothèque standard peut varier
  • Les développeurs ClojureScript aimeraient probablement supprimer, si possible, la documentation des différences avec Clojure, et si Wasm devenait une cible pratique pour ClojureScript, cette possibilité apparaîtrait

La méthode de tree shaking de Hoot Scheme

  • Après la prise en charge du GC, Wasm permet d’envisager la programmation DOM avec des langages comme Python, mais un usage grand public nécessite de petits modules
  • Le compilateur Hoot Scheme cible Wasm avec GC
    • L’unité de compilation minimale « main » actuelle fait environ 70 Ko
    • L’objectif est de descendre plus bas
    • Les unités de compilation auxiliaires qui importent depuis le module main des fonctionnalités de runtime comme les gestionnaires d’exceptions peuvent faire moins de 1 Ko
  • Le compilateur Hoot ajoute un prelude avant le code utilisateur
  • Le tree shaking intervient à plusieurs étapes
    • L’évaluation partielle peut supprimer les bindings inutilisés après n’en avoir évalué que les effets
    • fixing letrec effectue un travail similaire
    • CPS parcourt fréquemment le programme en ne suivant que les fonctions, valeurs et arcs de flot de contrôle référencés
    • Une passe explicite de dead-code elimination supprime les affectations inutilisées et sans effet qui peuvent apparaître après d’autres optimisations
    • Les définitions de la bibliothèque standard, écrites dans un WebAssembly proche du brut, ne sont incluses dans le binaire final que si nécessaire

Suppressions faciles et suppressions difficiles

  • Les définitions de procédures, comme les fonctions ou les closures, sont relativement faciles à traiter
    • Il suffit d’inclure uniquement les fonctions référencées par le code
    • Dans des langages comme Scheme, cela produit déjà un effet important
  • Trois difficultés apparaissent immédiatement
  • Modèle d’évaluation letrec*

    • La portée des définitions du prelude est récursive, mais ordonnée
    • Les valeurs des bindings peuvent appeler ou référencer des valeurs définies plus tôt, et peuvent aussi capturer des valeurs définies plus tard
    • Si l’évaluation de la valeur d’un binding doit référencer une valeur qui n’est définie que plus tard, c’est une erreur
    • Pour les procédures, cela ne pose généralement pas de problème, mais pour les définitions non procédurales, le compilateur peut ne pas être capable de prouver la propriété « ne référence que des bindings antérieurs »
    • Dans ce cas, l’algorithme fixing letrec reloaded peut conserver des bindings affectés avec set!, et leur suppression nécessite une passe DCE délicate
  • vtable des types record

    • Certaines définitions non procédurales sont des types record
    • Un type record possède une vtable qui contient notamment la manière d’afficher le record ou de tester ses instances
    • Les callbacks de vtable peuvent maintenir beaucoup de code en vie même lorsqu’ils ne sont pas réellement utilisés
  • Fonctions d’affichage polymorphes

    • Les fonctions polymorphes comme display élargissent fortement le périmètre de code nécessaire
    • Appeler display pour afficher une chaîne fait venir toute l’infrastructure d’I/O bufferisée
    • Comme display peut afficher n’importe quoi, il peut aussi embarquer du code pour divers cas comme les bitvectors, les paires, etc.
    • Appeler write-string, qui ne traite que les chaînes, permet d’éviter le code général d’affichage des données, mais inclut tout de même l’infrastructure générale d’I/O bufferisée comme les ports

Le tree shaking optimal est un problème d’analyse de flux

  • Le tree shaking optimal est au fond un problème d’analyse de flux
  • S’il n’existe jamais de bitvector dans un programme, le code qui traite les bitvectors dans display peut devenir du code mort
  • Pour le savoir, il faut connaître les types d’arguments avec lesquels display est appelé, ce qui nécessite une analyse de flux de haut niveau
  • En Python, le problème devient plus difficile
    • Le dispatch orienté objet est de la programmation d’ordre supérieur, car ce que signifie foo.bar dépend de ce qu’est foo
    • La résolution en Python est plus dynamique qu’en Scheme, et des méthodes comme __getattr__ peuvent être utilisées partout
    • En pratique, l’analyse de flux pourrait peut-être exclure ces résolutions dynamiques
    • La cible du tree shaking en Python n’est pas un grand terme avec des bindings lexicaux, mais un ensemble complexe de modules
    • Cela ressemble à JavaScript, mais Python ne dispose pas d’un écosystème établi de bundlers avec tree shaking

Les conditions pour les toolchains de langages Wasm sur le Web

  • Wasm GC peut rendre possible la programmation DOM avec des langages autres que JavaScript
  • Pour déboucher sur un usage grand public, les modules Wasm produits doivent être petits
  • Chaque toolchain de langage nécessite des investissements importants
  • Ces investissements prennent souvent la forme de toolchains alternatives, incluant des algorithmes expérimentaux de tree shaking
  • Les bibliothèques standard alternatives doivent être conçues pour que le tree shaker fonctionne mieux

1 commentaires

 
GN⁺ 2024-04-15
Commentaires sur Hacker News
  • En gardant le blob Wasm d’openEtG (moteur de jeu de cartes) sous les 400 KB, une grande partie de la logique, comme la génération du texte des cartes, a été déplacée dans le Wasm, et écrite en Rust
    Pour réduire la taille, il a fallu gérer cela en utilisant de l’arithmétique à virgule fixe plutôt que des flottants, en remplaçant les hash maps par des vecteurs, en évitant les chaînes de caractères, en utilisant de petits allocateurs comme talc, et en réduisant les dépendances
    Seuls rand et fxhash sont utilisés, et rand pourrait probablement aussi être supprimé ; fxhash ne sert qu’au hachage de l’état du jeu pour vérifier une éventuelle désynchronisation
    Le nombre de variantes d’instanciations génériques a aussi été réduit : puisque Vec est déjà présent, cela évite d’introduire en plus des types comme Box<[i16]>, et la suppression des flottants et des hash maps a également aidé à réduire la diversité des types
    Les algorithmes ont aussi été conçus en tenant compte de la taille ; par exemple, une table de correspondance compressée en bits encode le mécanisme adrenaline, où les créatures à faible attaque frappent davantage
    Le coût du stockage de valeurs non compressées a été comparé à celui de la logique de décodage, et pour l’évaluation de l’IA, 64 s’encode plus efficacement que 128 en WebAssembly, d’où l’utilisation d’une précision fixe sur 6 bits
    Le mécanisme de ciblage était auparavant une sorte d’AST où chaque prédicat était un enum et où les AND/OR étaient représentés comme des slices d’expressions ; désormais, les expressions sont encodées en notation polonaise dans des entiers 32 bits, avec 2 bits pour les AND/OR et 6 bits pour les prédicats
    Ici, la notation polonaise s’est révélée meilleure que la notation polonaise inversée, car elle permettait l’évaluation court-circuitée des AND/OR

    • Il est intéressant de voir que, même si Wasm dispose nativement de types à virgule flottante, l’arithmétique à virgule fixe a permis d’économiser pas mal d’espace
      Au travail, je me demande si cela pourrait aider sur des problèmes où l’on connaît l’exigence de résolution maximale, par exemple lorsqu’une précision de position inférieure au millimètre n’est pas nécessaire, donc je serais curieux d’en entendre davantage sur ce sujet
    • Un autre élément utile a été l’usage de wasm-opt de binaryen
      Cela semble réduire de manière fiable la taille du Wasm d’environ 20 à 30 % : https://github.com/WebAssembly/binaryen
      Lorsqu’on sert un bundle Wasm au navigateur, il est aussi utile d’utiliser la compression Brotli et de configurer le serveur web pour servir les fichiers compressés en Brotli
      Avec nginx, cela se fait avec une modification d’une seule ligne, et Brotli réduit la taille du bundle Wasm d’environ 3 fois, bien mieux que gzip
    • J’ai créé la bibliothèque de deep reinforcement learning en C++ RLtools ainsi que des exemples Wasm (https://rl.tools), et sans faire la moindre optimisation de taille, l’ensemble faisait déjà autour de 200 à 300 KB
      Cela inclut l’inférence et la rétropropagation pour le deep learning, des algorithmes de reinforcement learning, ainsi que de la simulation dynamique
      Ce n’est pas encore vraiment problématique, mais je suis curieux de voir jusqu’où on pourrait réduire ça, donc j’aimerais essayer bientôt
    • Je pensais que JavaScript utilisait des doubles pour tous les nombres ; je me demande donc si Wasm est complètement différent sur ce point
      J’aimerais comprendre comment il est possible d’économiser de l’espace en évitant les flottants au profit de l’arithmétique à virgule fixe
    • J’aimerais bien voir des chiffres sur l’espace réellement économisé
      En particulier, je n’ai pas l’impression que le fait d’utiliser Vec comme un Box puisse conduire à un gain énorme
  • Le nom tree shaking semble être un terme assez mal choisi
    Le compilateur Virgil appelle cela une « analyse d’accessibilité » et l’intègre dans son modèle de compilation
    Le compilateur parse le programme et le code des bibliothèques, effectue la vérification de types et exécute le code d’initialisation, mais ensuite il parcourt depuis le point d’entrée principal, n’analyse que le code accessible et ne place que celui-ci dans le binaire final
    Il génère très bien aussi des programmes avec une seule fonction main et sans système d’exécution, celui-ci n’étant nécessaire que pour les traces de pile et le ramasse-miettes, donc on peut l’omettre si on le souhaite

    • Treeshaker, comme nom d’outil de déploiement d’applications, vient peut-être de Lisp
      Le premier exemple que j’ai trouvé est l’outil Treeshaker de Lucid Common Lisp 4.1 en 1992, qui était une implémentation commerciale de Common Lisp pour UNIX
      Lucid CL avait le concept d’image, un dump mémoire sauvegardé du tas Lisp en cours d’exécution, et les applications étaient composées de l’image et du runtime
      Comme l’image contenait généralement presque tout le code et toutes les données en mémoire, on a voulu créer des images plus petites pour la distribution, et Treeshaker supprimait le code et les données jugés « non utilisés » avant de sauvegarder l’image
      Il élaguait les connexions dans le graphe des données et du code Lisp accessibles, puis le GC ou du code spécialisé collectait les déchets pour réduire la mémoire avant de produire une image plus petite
      Treeshaker n’était donc pas un outil de compilation, mais un outil supprimant le code et les données inutilisés du tas Lisp
      L’image Lisp de base incluait même le compilateur, l’interpréteur et l’implémentation du REPL ; ainsi, si l’on interrompait un programme en cours pour entrer dans le REPL, tout le code présent dans le tas restauré depuis l’image restait encore utilisable
      Il était donc logique de supprimer aussi le compilateur ou le REPL
    • Le terme généralement correct est élimination du code mort (dead-code elimination) : https://en.wikipedia.org/wiki/Dead-code_elimination
      L’analyse d’accessibilité est souvent utilisée pour déterminer quel code peut être supprimé, mais l’analyse elle-même ne retire pas le code ; la suppression intervient à l’étape suivante
      Le « tree shaking » implique en général une suppression au niveau des fonctions, alors que l’élimination du code mort peut aussi se faire à un niveau bien plus fin, comme la suppression de branches conditionnelles, et peut s’appuyer sur diverses analyses statiques
    • Les logiciels ont beaucoup de noms mal choisis, mais personnellement je trouve que tree shaking est un excellent nom
      La première fois que je l’ai vu, j’en ai immédiatement compris le sens sans recherche supplémentaire
      On secoue un arbre pour faire tomber ce qui y tient lâchement ; ici, il était clair que cela signifiait que les paquets inutilisés étaient « secoués » hors de l’arbre
    • Je n’ai jamais pensé que tree shaking faisait référence à un arbre au sens botanique, mais plutôt à l’arbre comme structure de données
      Si l’on imagine le diagramme du code source comme un objet physique, ce qui n’est pas accessible depuis la racine tombe quand on le secoue
      Cela ne diffère pas énormément de l’analyse d’accessibilité ; c’est simplement une formulation qui évoque davantage un raisonnement spatial
    • Il y a beaucoup de code, et c’est un arbre
      Une partie du code n’est pas reliée au tronc qu’est le point d’entrée
      Le tree shaking retire les parties non connectées, c’est-à-dire les feuilles lâches et les branches mortes
  • Si la toolchain de compilation d’un langage peut produire du Wasm utile en quelques Ko seulement une fois transmis sur le réseau, cela ouvre de nouvelles possibilités
    Des binaires extrêmement petits ouvriraient de nouveaux cas d’usage pour Wasm, et WasmGC aide clairement
    Java et Kotlin peuvent aussi déjà faire plutôt bien aujourd’hui avec environ 2 à 3 Ko : https://developer.chrome.com/blog/wasmgc, https://twitter.com/bashorov/status/1661377260274720770
    Il faut cependant faire attention, car selon les API utilisées, une grande quantité de code peut être embarquée
    Malgré cela, grâce à WasmGC, ces langages sont déjà bien meilleurs que C++ et Rust en taille de code, puisqu’ils n’ont pas besoin d’ajouter quelques Ko de code de gestion mémoire au bundle

    • Je me demande si WasmGC aidera aussi Rust
      Cela pourrait être utile pour manipuler des objets JavaScript, et peut-être aussi servir d’allocateur mémoire alternatif moins efficace
      Cela dit, l’allocateur Rust par défaut en Wasm sera probablement suffisant dans la plupart des cas
      Dès qu’on commence à optimiser la taille, qu’on utilise wasm-opt et qu’on compresse avec Brotli, on peut faire tenir une énorme quantité de code sous les 100 Ko en téléchargement
      C’est une erreur de comparer directement le coût de 100 Ko de Wasm à celui de 100 Ko de JavaScript bundle : JavaScript est plusieurs fois plus lent à parser et à initialiser
      Le temps de téléchargement est un coût réel, mais pour le délai d’affichage initial, 100 Ko de Wasm valent bien mieux que 100 Ko de JavaScript
      Cela dit, plus c’est petit, mieux c’est, et il est enthousiasmant de voir Java, Kotlin, C#, Python ou Go devenir des langages pratiques pour les applications web
      Je me demande aussi quelle sera la taille des applications réelles
      La plus grande différence viendra probablement de la conception des frameworks, et la comparaison de virtual DOM sera toujours plus complexe et plus lente que des bibliothèques de composants réactifs comme Svelte, SolidJS ou Leptos en Rust
      Si WasmGC est pris en charge partout, le choix du framework web aura sans doute bien plus d’impact sur les performances que le langage
  • Je me demande si l’affirmation selon laquelle « Wasm rend la programmation DOM envisageable dans des langages autres que JavaScript » est vraiment juste
    À ce que je sais, pour manipuler le DOM dans un langage comme Rust, il faut malgré tout des bindings qui sérialisent les appels destinés à être exécutés côté JavaScript
    Avec le Wasm dans sa forme actuelle, j’ai l’impression qu’on reste lié à JavaScript

    • Il est important d’inclure la condition précédente « avec le GC »
      En théorie, il devient alors possible de récupérer les fonctions du DOM au runtime et de les appeler à partir de références d’objets DOM, en contournant JavaScript pour invoquer directement le runtime
      Je ne sais pas avec certitude si c’est réellement faisable, mais le GC fournit au moins le mécanisme préalable nécessaire pour aller dans cette direction
    • J’ai récemment essayé Leptos pour Rust, un framework très rapide pour créer des frontends web en Wasm, et franchement ça a l’air plutôt bien
      En gros, c’est similaire à SolidJS
      #[component] fn App() -> impl IntoView { let (count, set_count) = create_signal(0); view! { "Click me: "{move || count()} } }
      Il y a un surcoût de taille Wasm par rapport à JavaScript, mais ce n’est pas dramatique
      Après wasm-opt et compression Brotli, le bundle Wasm de cette appli compteur faisait 37KB, donc dans une plage comparable à React, tout en étant bien plus rapide une fois exécuté
      Je n’ai pas essayé de manipuler directement le DOM, mais pour des composants classiques ça semble bien convenir
    • Si j’ai bien compris, l’une des intentions de WASI est de fournir une API qui accède directement au DOM
      Par exemple, un DOM implémenté en Rust pourrait être utilisé depuis un module Wasm écrit en Rust, sans exécution de JavaScript
      Cela dit, certaines parties de l’API DOM sont spécifiées en fonction de la sémantique de JavaScript, ce qui complique les choses ; c’est probablement pour cela qu’on avance d’abord sur des éléments moins hérités de JavaScript, comme les requêtes HTTP, les sockets TCP ou l’accès au système de fichiers
  • L’expression « élimination de code mort » existe depuis longtemps, alors je me demande pourquoi le terme tree shaking est apparu

    • « Élimination de code mort » désigne en général une optimisation de compilateur à petite échelle qui supprime des fragments de code inatteignables à l’intérieur d’une fonction
      Le « tree shaking » désigne une analyse globale du programme qui élimine des modules entiers et des fonctions jamais appelées
      Conceptuellement c’est la même chose, mais comme les concepteurs de compilateurs doivent souvent les implémenter séparément, avoir deux noms est utile
    • Le fait qu’un mot survive et devienne dominant n’a pas grand-chose à voir avec son ancienneté
      Tree shaking évoque quelque chose de parlant, se dit plus facilement et paraît plus accessible que « élimination de code mort », ce qui explique sans doute pourquoi le terme est devenu plus populaire
      En cherchant lequel des deux termes est apparu en premier, j’ai trouvé la plus ancienne occurrence de « dead-code elimination » dans un article de 1973 : https://research-repository.st-andrews.ac.uk/bitstream/handle/10023/22636/NicholasAlexandrakisMScThesis1973_original_C.pdf?sequence=1
      Sur Google Scholar, je n’ai trouvé aucun usage de « tree shaking » ou « tree shaker » en informatique ; c’était surtout lié aux arbres fruitiers et aux agrumes
      La discussion la plus ancienne que j’ai trouvée semblait être un message sur comp.lang.lisp : https://groups.google.com/forum/#!topic/comp.lang.lisp/pspFr1XByZk
    • Si l’expression vient vraiment du contexte Lisp, c’est peut-être parce qu’on y accorde autant d’importance à la suppression des données et métadonnées inutiles qu’au code exécutable inutile
    • L’expression élimination de code mort est bien meilleure
    • C’est une expression plus imagée, plus vivante, et plus naturelle à l’oral que « élimination de code mort », donc il n’est pas difficile de comprendre pourquoi elle s’est répandue
  • J’ai l’impression que la métaphore du tree shaking vient peut-être de certaines méthodes de récolte des arbres fruitiers
    On secoue l’arbre et les fruits mûrs tombent
    Cela dit, pour la récolte on veut ce qui tombe, alors que lors de l’enregistrement d’une image sérialisée on jette justement ce qui est tombé, donc ce n’est pas une métaphore si parfaite

    • En général, ce qu’on récolte en secouant, ce sont plutôt les fruits à coque
      Les fruits sont plus délicats et s’abîment s’ils tombent de trop haut, donc on les cueille le plus souvent à la main
      Le fait de secouer est assez brutal, mais cela n’endommage pas l’arbre
  • Ma philosophie consiste à optimiser au maximum la taille et les performances en JavaScript par la simplification des algorithmes et de la conception, afin de dégager de la marge en taille de bundle et en CPU pour le code qui a besoin de calcul brut
    Dans le projet ClubCompy, j’utilise Wasm pour implémenter un système de fichiers FAT au-dessus du stockage local, et cela s’est révélé très coûteux en calcul
    Plus tard cette année, je prévois aussi d’utiliser Wasm quand je réintroduirai la détection de collision de sprites précise au pixel près
    La première implémentation était en JavaScript pur, et quand les 256 sprites à l’écran entraient en collision les uns avec les autres, le framerate tombait sous 1 fps
    Je pense qu’on peut traiter ça quasiment gratuitement sur un worker thread, sans impact sur les performances

    • La détection de collision au pixel près paraît séduisante au premier abord, mais peut en pratique être désagréable une fois en jeu
      Si la plupart des jeux 2D utilisent des boîtes de collision rectangulaires, ce n’est pas sans raison : cela permet au joueur de prédire plus facilement s’il y aura collision ou non
      Avec une collision au pixel près, un même déplacement peut provoquer une collision ou non selon la phase du cycle d’animation
      C’est frustrant quand un mouvement qui passait toujours auparavant échoue simplement à cause d’un mauvais timing de l’animation
      En plus, le pixel près n’est pas toujours réaliste : de petits détails d’un sprite peuvent représenter du tissu ou des cheveux, qui en réalité ne devraient pas produire une collision rigide
      Les sprites se déplacent aussi souvent de plusieurs pixels par frame, ce qui augmente la probabilité qu’une collision au niveau des pixels traverse l’autre objet
      Une détection de collision simple et prévisible reste en général la meilleure solution
    • Si l’on tombe à 1 fps avec 256 collisions, le goulot d’étranglement est l’algorithme, pas le langage de programmation
      1 fps est un framerate qu’on devrait atteindre vers quelque 20 000 collisions
      Pour du pixel près, s’il y a assez de marge mémoire, une méthode simple consiste à rendre un canvas hors écran pour l’ensemble de l’aire de jeu, à dessiner chaque sprite avec un pochoir d’une couleur différente, puis à inspecter le résultat
      C’est linéaire et ne nécessite aucun partitionnement supplémentaire
      En revanche, les mesures anti-fingerprinting peuvent remplacer les bits de poids faible des données du canvas par du bruit, donc il faudra peut-être utiliser les bits de poids fort
  • Ce texte dit des choses justes. Wasm a un problème de taille du code
    Dans le navigateur, c’est un problème parce qu’il faut télécharger tout le code avant le démarrage du site, et c’en est aussi un dans une architecture serverless, car le code est chargé depuis un stockage à froid vers un serveur donné au moment où il devient nécessaire pendant que le client attend
    Le tree shaking peut aider, mais cela semble n’être qu’une optimisation progressive
    Fondamentalement, si les programmes Wasm deviennent volumineux, c’est parce qu’ils doivent embarquer en bloc leur propre runtime de langage et leur bibliothèque standard
    À l’inverse, pour JavaScript, l’implémentation et les bibliothèques de base sont fournies par le navigateur
    On pense facilement qu’un navigateur ne peut pas embarquer à l’avance tous les runtimes de tous les langages, mais il faut envisager une autre approche : les bibliothèques partagées et l’édition de liens dynamique
    WebAssembly prend en charge l’édition de liens dynamique, et plusieurs modules Wasm peuvent être chargés en même temps et s’appeler mutuellement
    Mais beaucoup de toolchains Wasm n’essaient pas de le prendre en charge et sont conçues pour faire une édition de liens statique du programme entier et du runtime du langage dans un unique énorme module
    Pyodide (CPython sur Wasm) est un contre-exemple, car il est actuellement conçu avec l’édition de liens dynamique à l’esprit
    C’est précisément pour cela que Cloudflare Workers a récemment pu ajouter la prise en charge de Python comme citoyen de première classe : https://blog.cloudflare.com/python-workers
    Du point de vue du responsable technique de l’ensemble de la plateforme Workers, tous les Workers exécutés sur la même machine partagent un unique runtime Pyodide compilé, il n’est donc pas nécessaire de le charger séparément pour chaque Worker
    Si l’édition de liens dynamique était plus largement prise en charge, on pourrait imaginer qu’un navigateur précharge des runtimes de langages populaires, voire des bibliothèques populaires, et que toutes les pages web qui en ont besoin partagent la même copie de code en lecture seule
    Ces runtimes continueraient à s’exécuter dans une sandbox, donc le navigateur n’aurait pas besoin de leur faire confiance, seulement de les fournir
    Cela permettrait de créer des navigateurs avec une prise en charge « intégrée » de langages autres que JavaScript, sans obliger les mainteneurs du navigateur à valider complètement ou à assumer pleinement l’implémentation du langage

    • L’idée que le navigateur précharge plusieurs runtimes et bibliothèques populaires pour que les pages web les partagent ressemble à l’ancienne idée selon laquelle, si des frameworks JavaScript courants étaient fournis via un CDN commun, ils seraient déjà présents dans le cache de la plupart des navigateurs, donc pas besoin de les télécharger et la taille du framework importerait peu
      Je ne connais pas bien le sujet, mais je crois comprendre que cela n’a pas vraiment marché
      Il existait trop de versions de bibliothèques, si bien que chaque version particulière n’était en pratique pas si répandue, puis les navigateurs ont évolué vers un cloisonnement des caches par site ou par origine pour des raisons de confidentialité
      Ce n’était peut-être pas pensé autour du cache, mais c’est un problème délicat qui relève davantage d’un problème social que d’un problème technique
      Ajouter davantage de runtimes au support de base du navigateur augmente aussi la barrière à l’entrée pour les nouveaux navigateurs, sans pour autant permettre de prendre en charge toutes les bibliothèques et tous les runtimes que les gens veulent
      Si chacun apporte les siens et qu’on compte sur le cache, reste alors la question de savoir comment éviter les problèmes déjà rencontrés avec la mise en cache des bibliothèques JavaScript
    • Même une prise en charge limitée de l’édition de liens dynamique pourrait avoir un effet assez important
      Par exemple, une bibliothèque partagée Go pourrait contenir le runtime et les parties essentielles de la bibliothèque standard utilisées par de nombreux programmes
      Même sans prise en charge des bibliothèques dynamiques dans l’application elle-même, on pourrait réduire la taille de tous les programmes Go, et le runtime du langage n’aurait pas besoin d’optimiser l’espace de manière extrême
      Puisqu’il est déjà chargé, et que dès qu’un programme utilise ne serait-ce qu’une fonction il ne constitue plus de l’espace gaspillé
      Cela modifierait le modèle de coût de l’optimisation de la taille pour les programmes dans ce langage
      Les fonctions de la bibliothèque standard incluses deviennent en pratique gratuites dès lors qu’on utilise le langage, donc autant les utiliser
      Mais le problème se répète avec les bibliothèques et frameworks courants
      Lorsqu’on exécute sur Cloudflare, on voudrait aussi partager une bibliothèque standard Cloudflare pour Go
      Le problème est que le langage n’évolue pas au même rythme que le runtime
      La prise en charge de plusieurs versions du langage pourrait être limitée, ou les bibliothèques partagées pourraient s’accumuler avec le temps, réduisant l’effet de mutualisation entre applications
      JavaScript a un modèle de version où « on n’a pas le choix », avec une forte rétrocompatibilité et, parfois, le recours à des polyfills
      Cela peut convenir moins bien à d’autres langages
      Si le runtime veut vraiment réduire l’espace, il lui suffit de limiter la diversité des plugins
      Cela a suscité des plaintes, mais l’approche « il faut utiliser JavaScript » a plutôt bien fonctionné dans le navigateur
      Les langages basés sur WebAssembly n’ont peut-être pas besoin d’être si nombreux, et comme dans une situation de tour de Babel, la diversité a un coût
    • Je me demande s’il serait possible de faire du tree shaking basé sur le profilage afin de produire de petits modules ne contenant que le code nécessaire à l’application, et de récupérer les fonctionnalités moins utilisées via l’édition de liens dynamique au moment où elles deviennent nécessaires
      Si le tree shaking s’appuie sur des informations réelles de production, on pourrait sans doute supprimer beaucoup de code mort ou presque mort sans avoir à implémenter des algorithmes d’analyse statique sophistiqués
    • Une structure où le navigateur télécharge à l’avance les runtimes populaires risque au final de remplir le cache avec des centaines de versions de runtimes, chaque binaire n’étant utilisé que par un seul site
    • Je suis globalement d’accord, mais je veux souligner que, dans Wasm aussi, dès qu’on veut faire autre chose que du calcul pur, on finit par utiliser pas mal de JavaScript intégré au navigateur
      En particulier, dans un contexte comme Hoot, des éléments comme appendChild sont des fonctions externes appelées depuis Scheme : https://spritely.institute/news/building-interactive-web-pages-with-guile-hoot.html
      En théorie, on pourrait utiliser ainsi une grande partie de la bibliothèque standard JavaScript dans n’importe quel environnement Wasm
  • Zig est parfait pour ce type d’usage
    Personnellement, j’estime que la taille d’un fichier Wasm n’est pas un facteur important tant qu’elle reste inférieure à 100 KB, et qu’elle le devient au-delà du MB
    Un GC intégré est important pour certaines applications, mais pas pour toutes, et il vaut mieux construire les applications web sans GC
    Le facteur le plus important pour le succès des applications utilisant Wasm reste l’avantage en performances

  • J’exécute une application Blazor sur Cloudflare Pages ; les téléchargements sont rapides et les performances sont bonnes, mais le temps de chargement est terrible
    Je pensais qu’il était impossible de résoudre ça avec .NET, et le problème principal semble être que, dans les langages orientés objet, tout finit par être imbriqué par conception
    Il est aussi difficile de rivaliser avec l’ampleur des financements investis dans JavaScript, et JavaScript donne l’impression d’avoir le copier-coller comme fonctionnalité du langage, ce qui ressemble à une sorte de triche
    Troisièmement, même avec Blazor, on a toujours besoin de JavaScript et de compétences de ce côté-là, et je pense que c’est le problème principal

    • Ce n’est pas un problème d’orienté objet
      Le problème, c’est que les langages de cette époque reposaient sur la réflexion dans de nombreux cas d’usage
      Les gens travaillent activement à éliminer ces cas d’usage dans une grande partie de .NET et à les marquer comme sûrs pour la suppression du code inutilisé
      S’il est possible d’appeler une méthode via réflexion, il est difficile de savoir ce qu’on peut retirer en toute sécurité
      Même s’il semble n’y avoir aucun appel à Foo.Bar(), que faire si quelqu’un exécute Reflection.getClass(someClass).runMethod(someVar) et que ces variables sont définies sur "Foo" et "Bar" ?
      Par exemple, Dart n’autorise pas la réflexion dans les applications compilées à l’avance, ce qui permet de supprimer en toute sécurité le code inutilisé : https://docs.flutter.dev/resources/faq#does-flutter-come-with-a-reflection-mirrors-system
      Dart est un langage orienté objet, mais il évite la génération de code à l’exécution et la réflexion à l’exécution, en privilégiant la génération de code à la compilation
      .NET va aussi dans cette direction, mais cela ne se fait pas du jour au lendemain
      Cela dit, comme d’autres l’ont indiqué, les langages non JavaScript ont aussi le problème de devoir embarquer eux-mêmes les parties des fonctionnalités de bibliothèque standard incluses dans le runtime JavaScript du navigateur qu’ils utilisent
    • Blazor n’est pas particulièrement bon sur cet aspect
      Le modèle de packaging actuel ne tire pas vraiment parti des capacités de trimming .NET, en raison des limites du packaging de Wasm au-dessus de Mono
      Pour voir jusqu’où on peut réellement réduire, mieux vaut compiler une application classique en AOT, et on obtient alors un petit binaire
      Le support expérimental de la cible Wasm NativeAOT-LLVM dans dotnet/runtimelab offre une taille de bundle bien plus petite et des performances nettement meilleures, mais comme il se trouve encore sous dotnet/runtimelab et non dotnet/runtime, je ne sais pas quand cela deviendra utilisable