2 points par GN⁺ 2023-07-13 | 1 commentaires | Partager sur WhatsApp
  • Le prototype d’emprunt par régions de Vale a réussi à compiler pour la première fois, ce qui permet de vérifier sur de vrais programmes une approche de sûreté mémoire combinant références générationnelles et régions
  • Les développeurs peuvent écrire du code d’une manière proche du C/C++, puis n’appliquer pure et l’emprunt par régions qu’aux endroits nécessaires afin de réduire le surcoût des vérifications générationnelles
  • Le premier programme Vale à zero-check était un exemple de Cellular Automata pour la génération de niveaux de roguelike, et l’assembleur produit est arrivé à un niveau presque identique au mode unsafe_with_bounds
  • Dans les benchmarks, safe_fastest n’a montré aucun ralentissement observable par rapport à unsafe_with_bounds, tandis que unsafe_no_bounds était 1.18 ± 0.01 fois plus rapide que les deux autres modes
  • Il ne s’agit pas encore d’une comparaison directe avec C/Rust, et il reste à traiter le bruit d’optimisation de LLVM, la maturité du prototype et l’absence de prise en charge des inline data, ainsi qu’à finaliser le pre-optimizer propre à Vale et les fonctionnalités de régions

Combiner références générationnelles et emprunt par régions

  • L’approche de sûreté mémoire de Vale vise à se passer de reference counting, de garbage collection par traçage et de borrow checking
  • La structure de base consiste à laisser le développeur écrire le programme d’une manière proche du C ou du C++, tandis que les generational references de Vale assurent la sûreté mémoire
  • En appliquant ensuite pure et le region borrowing, il devient possible d’éliminer l’essentiel du surcoût des vérifications générationnelles
  • En y ajoutant jusqu’au style linéaire, l’auteur estime qu’il est possible de ramener les vérifications générationnelles à zéro dans le code Vale
  • L’emprunt par régions est entièrement opt-in, ce qui permet d’écrire d’abord librement puis de l’ajouter plus tard uniquement aux parties qui ont besoin d’optimisation
  • L’objectif est une structure où, dans un même programme, certaines parties peuvent être flexibles comme en Java, d’autres rapides comme en Rust, ou se situer entre les deux

Ce qu’il a fallu pour construire le prototype

  • Ces dernières années, le travail a consisté à poser les bases du compilateur pour prendre en charge à la fois un système d’emprunt fondé sur les régions et les generational references
  • Le système d’emprunt lui-même est complexe, et il nécessitait des full generics plus puissants que les templates existants
  • Pour que les régions et les références générationnelles fonctionnent naturellement ensemble, une nouvelle étape du compilateur était également nécessaire
    • En interne, les régions sont réduites à des entiers de « pure height »
    • Les paramètres génériques de région sont représentés par des valeurs négatives, la région par défaut par 0, et chaque bloc pure par un entier positif croissant
  • Le prototype des régions a été achevé il y a quelques mois ; il reste encore des parties brutes, mais quelque chose a enfin pu être compilé avec succès
  • Cela a permis de créer le premier programme Vale à zero-check
    • Le flag du compilateur --print_mem_overhead true permet de compter le nombre de vérifications générationnelles dans le programme

Premier programme zero-check et comparaison de l’assembleur

  • Le premier programme était un exemple de Cellular Automata générant des niveaux pour un jeu de type roguelike
  • Une petite erreur dans le code du compilateur peut suffire à ajouter des instructions dans l’assembleur produit et créer ainsi un surcoût artificiel dans le programme final
  • Pour remonter à la source du problème, l’assembleur produit a été comparé en continu au mode unsafe de Vale
    • unsafe_no_bounds : comme en C, toutes les protections de sûreté mémoire sont désactivées et des pointeurs bruts sont utilisés à la place des generational references
    • unsafe_with_bounds : comme en Rust, des bounds checks sont ajoutés aux accès aux tableaux
  • Après plusieurs mois de traque des écarts, l’assembleur obtenu est devenu presque identique à celui du mode unsafe_with_bounds
  • La seule différence attendue était l’ajout d’un numéro de génération pseudo-aléatoire en tête de chaque allocation, qui n’était en pratique jamais lu lors des vérifications générationnelles
    • En interne, un registre monotone croissant est utilisé pour conserver de bonnes performances
    • L’ajout d’isolates ou de unique references pourrait éliminer cette différence

Résultats des benchmarks et conditions de mesure

  • Le résumé des benchmarks est le suivant
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'
  • safe_fastest, le mode normal de Vale, ne montre aucun ralentissement par rapport au mode qui n’ajoute que des bounds checks
  • Dans cette mesure, cette approche n’introduit aucun surcoût observable
  • Pour l’essayer directement, il est possible de compiler la branche regions, de consulter les scripts de benchmark et de poser des questions sur le serveur Discord
  • Les conditions de mesure comportent des limites explicites
    • Il ne s’agit pas d’un benchmark comparant directement à des langages comme C ou Rust
    • Ces compilateurs disposent d’années d’optimisations propres, ce qui pourrait brouiller les variables de l’expérience
    • Pour isoler les différences liées à l’approche de sûreté mémoire, la comparaison se fait avec unsafe_no_bounds et unsafe_with_bounds
    • L’environnement de mesure était un Razer Blade 15" 2018 avec SSD de 512 Go sous Ubuntu 22.04
    • L’outil de mesure était hyperfine, exécuté à l’intérieur de cset shield

Bruit d’optimisation observé sur de plus gros programmes

  • Sur des programmes plus importants, un optimizer noise assez marqué a été observé
    • Ce phénomène est différent du bruit de benchmark, car les mesures montraient des temps d’exécution très cohérents, du type ± 0.01
    • Un petit changement dans une zone pouvait faire varier la mesure dans un sens ou dans l’autre
  • Un cas a même donné de façon cohérente un surcoût négatif de 1.13 ± 0.01 après modification de la taille du numéro de génération
    • Le programme contenait pourtant peu de numéros de génération, ce qui rendait ce résultat étrange
    • Il est possible que des changements d’allocation de registres aient eu plus d’effet sur les performances que les différences sémantiques elles-mêmes
  • Dans un programme plus grand, un petit jeu roguelike, l’optimiseur n’a pas réussi à fusionner deux branches identiques à l’intérieur d’un if et a aussi manqué d’autres optimisations évidentes
  • L’effet exact de la présence d’un entier jamais lu n’est pas clair, et il pourrait même s’agir d’un bug LLVM
  • Ces résultats suggèrent qu’un pre-optimizer propre à Vale, comparable au MIR de Rust, pourrait être nécessaire
    • LLVM a été davantage conçu avec le C en tête
    • Si LLVM interprète les generational references comme un motif d’accès intentionnel à de la mémoire libérée, il pourrait considérer cela comme de l’undefined behavior

Applicabilité et travail restant

  • Les références générationnelles et les régions peuvent, une fois combinées, offrir une approche de sûreté mémoire très rapide
  • Les domaines logiciels auxquels cette approche pourrait convenir ont les caractéristiques suivantes
    • recherche d’une latence plus prévisible qu’avec un tracing garbage collection
    • recherche de meilleures performances et d’une meilleure cache friendliness qu’avec le reference counting
    • volonté de prototyper et d’itérer plus facilement qu’avec le borrow checking
  • Il reste du travail avant que Vale puisse être comparé frontalement à C ou C++
    • L’optimiseur LLVM a des difficultés à inférer la génération et l’immutabilité, ce qui impose un pre-optimizer propre à Vale
    • Vale doit actuellement prendre en charge les inline data au lieu de la solution temporaire qui place toutes les struct sur le heap
    • Le benchmark ci-dessus n’utilisait pas de struct, donc l’absence de prise en charge des inline data n’a pas affecté ce résultat
    • Les fonctionnalités de régions en sont encore au stade de prototype ; il faut lisser les angles et réduire la dette technique avant une fusion dans la branche principale
  • Après la fusion, l’objectif est de faire en sorte que la bibliothèque standard utilise les régions, afin que le code principal des utilisateurs puisse en bénéficier même sans les employer directement
  • Les mesures actuelles montrent qu’un programme zero-check est possible et peut atteindre la vitesse attendue

1 commentaires

 
GN⁺ 2023-07-13
Commentaires sur Hacker News
  • J’ai téléchargé Vale pour l’essayer, et la première impression n’a pas été bonne : si on lance le compilateur valec sans argument, il affiche directement "(panic)"
    panic est un terme très fort, et à mon avis il faut l’éviter dans une situation normale de gestion d’erreur. Quand un programme affiche qu’il a paniqué, cela donne l’impression d’une situation non maîtrisée, ce qui laisse un mauvais arrière-goût
    J’ai ensuite voulu afficher l’aide des arguments en ligne de commande, mais elle était en pratique absente. J’ai enregistré l’exemple Hello World du site dans hello.vl, puis j’ai lancé valec hello.vl, et j’ai obtenu Unknown subcommand
    J’ai donc essayé valec build hello.vl, qui a répondu Unrecognized input: hello.vl puis à nouveau (panic). Même valec help n’a servi à rien, donc j’ai fini par abandonner. Je ne vois pas comment cet outil est censé s’utiliser

    • Désolé. Il semble que le fichier d’aide ne s’affiche plus correctement. Si vous faites directement un cat sur valec-help-build.txt inclus dans le téléchargement, vous y trouverez probablement ce que vous cherchez
      Le compilateur est encore très brut de décoffrage. D’août à mai, je me suis concentré à 100 % sur le prototypage des regions, et ce que vous voyez maintenant, c’est la dette technique accumulée pendant ce processus. Cela inclut l’absence de tests d’intégration pour le système d’aide
      Depuis 1 à 2 mois, je travaille à rembourser cette dette, mais on n’est pas encore revenu au niveau de la version 0.2. Si vous avez besoin de plus d’aide, faites-le moi savoir ou passez sur le serveur Discord, il y a beaucoup de gens prêts à aider
    • J’ai l’impression que Vale est encore essentiellement en phase de R&D. On peut au mieux espérer qu’un commit précis sur une branche précise fonctionne ; on n’en est pas vraiment au stade où n’importe qui peut télécharger le compilateur et construire quelque chose avec
      Cela dit, le README ne le montre pas clairement et dit plutôt “Try Vale”, donc c’est ambigu. En l’état, ça ressemble davantage à de la R&D ou à une preuve de concept
    • C’est moins un bug qu’un problème d’interface utilisateur. Si c’est expérimental, je pense qu’on peut aussi être un peu indulgent avec les vrais bugs
      Même du point de vue de l’interface ou des bugs, l’expérience consistant à déboguer du C++ vieux de 40 ans avec un gdb vieux de 35 ans peut largement rivaliser avec n’importe quel langage expérimental. Par exemple, l’affichage de funcname()::staticvarname est une interface étrange et échoue une fois sur deux. Et inutile de parler des systèmes de build C++
      Pour une technologie expérimentale, on peut critiquer le concept, mais une interface utilisateur encore rugueuse me paraît acceptable jusqu’à un certain point
    • Le README sur GitHub explique comment utiliser le compilateur
      https://github.com/ValeLang/Vale#building-a-vale-program
    • Pour un logiciel encore en phase alpha, c’est à peu près ce à quoi on peut s’attendre
  • Une latence plus prévisible qu’avec un garbage collection par tracing, de meilleures performances et une meilleure affinité cache que le comptage de références, et un prototypage ainsi que des itérations plus simples qu’avec un borrow checker : je suis plus qu’intrigué, je suis vraiment intéressé
    J’ai même commencé à m’abonner au flux RSS : https://verdagon.dev/rss.xml

    • Enfin une nouvelle idée dans un langage compilé AOT qui ne se résume pas à “laissons simplement des bugs mémoire se produire de temps en temps”
  • Vale a besoin de davantage de sponsors
    https://github.com/sponsors/ValeLang
    J’aimerais profiter du fait que ce billet est en page d’accueil pour aider le projet à atteindre son objectif de 3 000 dollars par mois
    J’aimerais aider Evan à pouvoir travailler à plein temps là-dessus. Je suis moi-même sponsor. Un langage rapide, sûr et agréable pour le prototypage mérite d’être soutenu

    • Je me demande en quoi la répartition des revenus diffère entre le sponsoring GitHub et Patreon
  • Par “pré-optimiseur dédié à Vale, similaire à Cranelift pour Rust”, il semble sans doute vouloir parler de MIR, c’est-à-dire la représentation intermédiaire de niveau moyen. Il existe à ce sujet un bon billet de blog : https://blog.rust-lang.org/2016/04/19/MIR.html
    Cranelift est surtout un backend de compilateur orienté JIT, mais il pourrait en théorie remplacer LLVM. Un backend alternatif est aussi en cours de développement, avec certaines limites : https://github.com/bjorn3/rustc_codegen_cranelift

    • C’est probablement ça. Je vois Cranelift comme un optimiseur Rust pour WebAssembly
  • Une approche qui permet de ne pas se soucier de la gestion mémoire dans la majeure partie du code, tout en offrant la possibilité d’optimiser uniquement les chemins critiques avec des abstractions à coût nul, donne l’impression de réunir le meilleur des deux mondes
    C’est particulièrement vrai si, pour plus de confort, on échange non pas la sécurité mais seulement les performances

    • Je n’utilise que du C++, mais je ne me soucie pas du tout de la gestion mémoire
      La propriété partagée est un mauvais concept, donc je n’utilise pas non plus de smart pointers
      Les problèmes de gestion mémoire me paraissent globalement mineurs
  • Je continue à me demander ce que veut dire sûr dans le contexte des références générationnelles.
    Si j’ai bien compris, cela signifie empêcher les use-after-free et les double-free ? Si c’est le cas, le programme peut quand même échouer lors d’un accès mémoire si la génération attendue et la génération réelle ne correspondent pas.
    De ce point de vue, cela semble moins sûr que le comptage de références, le ramasse-miettes par traçage ou la vérification d’emprunt.

    • Les double-free sont évités par la propriété unique de Vale, c’est-à-dire la propriété unique au sens de C++, et les références générationnelles permettent de détecter de manière sûre les use-after-free.
      Si l’on tente d’accéder, via une référence, à une mémoire déjà libérée, on devrait obtenir de façon prévisible et sûre une erreur de segmentation ou un échec d’assertion. J’attends avec impatience une amélioration future qui remappera l’espace d’adressage virtuel, ce qui pourrait aussi éliminer les erreurs de segmentation.
    • C’est sûr au sens où l’on obtient une erreur de segmentation au lieu de laisser un pointeur pendant lire ou écrire une mémoire arbitraire.
      Cela dit, avec un index de génération, on devrait aussi pouvoir vérifier à l’exécution qu’un accès est valide avant de tenter l’accès réel. Je ne sais pas si c’est possible dans Vale.
    • C’est moins sûr que le GC, la vérification d’emprunt ou le comptage de références. Cela reste plus sûr que malloc/free, et il y a aussi d’autres avantages.
    • Je me le demande aussi. Je ne vois pas comment cela empêche les use-after-free et les double-free.
      La fonction check a besoin du numéro de génération de l’allocation, donc elle accède à l’allocation. Autrement dit, pour vérifier si une référence peut accéder à cette allocation, il faut d’abord accéder à cette allocation.
      Bien sûr, si l’allocation a déjà été libérée, le simple fait d’accéder à cette allocation et à son numéro de génération relève déjà d’un comportement indéfini, donc cela ne fonctionne pas.
      Cela me paraît tellement évident que je me demande si je rate quelque chose d’important, ou si la « sécurité mémoire » dont on parle ici a un sens complètement différent.
    • Si l’on demande si c’est moins sûr que ma définition volontairement restrictive de « sûr », alors oui, probablement.
  • Vale n’est pas un langage comme V. V a reçu une critique très sévère sur https://mawfig.github.io/2022/06/18/v-lang-in-2022.html, et comme les noms se ressemblent, je l’avais confondu avec Vale.
    Je laisse ça ici au cas où d’autres auraient fait la même erreur.

    • Cette « critique très sévère » n’est qu’une liste de petits bugs corrigés il y a un an.
      Le contenu de l’article n’est plus pertinent aujourd’hui, et pourtant il est toujours là ; c’est aussi l’unique billet de ce blog.
    • Cette « critique » ressemble davantage à un vieux spam que des opposants ou des trolls continuent d’utiliser. Une « critique » d’une version alpha du langage, en réalité un texte agressif, qui ne semble pas avoir d’autre valeur.
      Nous sommes maintenant en 2023, et V est aussi en bêta (0.4). De plus, la personne qui a écrit ce texte a utilisé un compte GitHub jetable pour publier cette critique/attaque, créer la polémique, puis disparaître.
      Le seul article publié sur ce blog est aussi une attaque contre V, il n’y a pas d’autres critiques. Les parties qui avaient une vraie substance ont déjà été corrigées[1].
      Si vous cherchez mawfig.github, vous verrez aussi qu’il a été diffusé à répétition sur HN, généralement pour dénigrer.
      [1]: https://github.com/vlang/v/issues/14803
      [1]: https://github.com/vlang/v/issues/14787
      [1]: https://github.com/vlang/v/issues/14786
  • Félicitations à Evan pour cette étape franchie. Je n’ai aucune expérience en conception de langages de programmation ni en compilation, mais j’aime lire les articles sur Vale.

    • Moi aussi, à peu près, mais j’aimerais qu’il ait un autre nom.
      Maintenant qu’il y a le Vale d’Evan et le Val de l’Adobe Software Technology Lab, chercher des ressources à ce sujet va devenir assez difficile.
      https://www.val-lang.dev
    • Moi non plus, je n’ai généralement pas les connaissances de base pour comprendre les articles, mais je trouve quand même cela intéressant.
    • Moi aussi, je trouve les articles excellents et j’attends avec impatience l’avenir de Vale.
  • « Vale est rapide : Vale est compilé en AOT vers LLVM, utilise un typage statique, emploie une nouvelle technique de références générationnelles pour une sécurité mémoire rapide et flexible, et deviendra bientôt encore plus rapide grâce à la vérification d’emprunt par régions »
    https://vale.dev/

  • J’ai l’impression d’écouter une dispute entre deux personnes qui dure depuis 5 ans.
    Quelqu’un peut expliquer ce qui se passe ici ? Cet article est beaucoup trop hermétique.