OCaml en 2021

Posté par  . Édité par Yves Bourguignon, chimrod, Ysabeau 🧶 🧦, Snark, Benoît Sibaud, Anonyme, Bruno Ethvignot, Quidam, tisaac, Michaël, dourouc05 et syntaxerror. Modéré par Benoît Sibaud. Licence CC By‑SA.
Étiquettes :
58
25
sept.
2021
Programmation fonctionnelle

La version 4.13.0 du langage OCaml est sortie le 24 septembre 2021, sept mois après OCaml 4.12.0 sortie le 24 février 2021.

OCaml est un langage fonctionnel de la famille des langages ML (dont font partie SML et F#). Il s’agit d’un langage fonctionnel multi‐paradigme fortement typé qui permet de mélanger librement les trois paradigmes : fonctionnel, impératif et objet. La plus grande spécificité d’OCaml dans le paysage des langages fonctionnels (Haskell, Rust, F#…) est probablement son système de module : les modules d’OCaml font partie intégrante du langage, et il est par exemple possible de décrire des modules paramétrés par d’autres modules (à travers des foncteurs).

La grande nouveauté de cette année 2021 est la convergence de l’environnement d’exécution entre la version standard d’OCaml et le prototype d’OCaml multi-cœur. Cette convergence amorce une nouvelle étape dans la transition vers OCaml multi-cœur. Au-delà des progrès vers OCaml multi-cœur, cette année 2021 a vu une de nombreuses avancées pour le langage OCaml et son compilateur que ce soit en termes d’architectures supportées, de messages d’erreurs, de fonctionnalités du système de types, mais aussi des améliorations de confort pour les programmeurs que ce soit au niveau des outils de profilage, de la gestion des avertissements ou de la bibliothèque standard.

Sommaire

La route vers le multi-cœur et OCaml 5.0

Une des limites de l’implémentation actuelle de l’environnement d’exécution d’OCaml est son utilisation d’un verrou global. Ce verrou empêche les applications multithreads de bénéficier du parallélisme des fils d’exécution (threads). Au cours du temps, il y a eu plusieurs tentatives d’enlever ce verrou. La dernière initiative a germé chez OCaml Labs vers 2014-2015. Pour éviter les échecs précédents, cette initiative a décidé de se concentrer sur deux points : une compatibilité descendante presque parfaite avec la version monocœur d’OCaml, et une intégration incrémentale dans la branche principale d’OCaml. Ce travail de fond a commencé à être visible dans OCaml 4.10.0. Mais il s’est notablement accéléré dans OCaml 4.12.0. Une grande partie du travail dans OCaml 4.12 et 4.13 a été consacrée à diminuer les divergences entre l’environnement d’OCaml multi-cœur et la version principale d’OCaml.

Par exemple, un des changements majeurs prévus pour OCaml multi-cœur est la gestion des pointeurs pointant en dehors de la mémoire gérée par OCaml, sans être gardés par des métadonnées (parce que, par exemple, ils ont été alloués par une bibliothèque C externe). Dans la version monocœur d’OCaml, ces pointeurs étaient gérés en gardant une trace des zones mémoires allouées par OCaml. En passant à un environnement d’exécution multi-cœur, cette stratégie devient prohibitive en coût de synchronisation. Ces pointeurs nus ne seront donc pas autorisés dans OCaml multi-cœur. Pour assurer une évolution en douceur, OCaml 4.12.0 a ajouté deux options de configuration : une option pour désactiver la gestion des pointeurs nus directement pour les audacieux ; et une version plus prudente qui rajoute un test dynamique de la présence de ces pointeurs nus. Cette dernière option est notamment utilisée pour tester toutes les bibliothèques et programmes disponibles sur Opam (le dépôt de paquets d’OCaml).

Un autre point important est la gestion de l’ordonnancement entre l’utilisateur et l’environnement d’exécution (runtime). Dans la version monocœur d’OCaml, l’environnement d’exécution reprend la main à chaque allocation. Cela lui donne l’occasion de vérifier si le Glaneur de Cellules (GC) à du travail à faire, ou s’il faut s’occuper de signaux en attente. Une conséquence est qu’il est possible d’écrire du code numérique qui n’alloue jamais et ne rend jamais la main à l’environnement d’exécution. En absence de parallélisme, ce comportement est plus une curiosité qu’autre chose. Mais pour multi-cœur OCaml, ce comportement égoïste n’est plus de mise. Dans sa conception actuelle, OCaml multi-cœur a une phase de GC en parallèle, pendant laquelle tous les fils d’exécution exécutent une passe de GC de manière synchrone. Il n’est donc pas question qu’un fil d’exécution bloque le GC de tous les autres fils. Le compilateur natif a donc été modifié dans OCaml 4.13.0 pour s’assurer qu’un fil d’exécution passe toujours la main à l’environnement d’exécution dans un temps borné.

Un élément qui commence à apparaître dans les discussions sur OCaml multi-cœur est que l’on se rapproche d’un point où il ne reste plus qu’à faire le grand saut et intégrer le runtime multi-cœur, et absorber les petites pertes de performances inévitables pour le code séquentiel.

La première version d’OCaml qui intégrera la prise en charge du multi-cœur sera OCaml 5.0. Cette nouvelle majeure commencera avec une période de transition durant laquelle la branche 4 sera maintenue activement.

Cette première version d’OCaml multi-cœur n’intègrera pas la partie la plus innovante de la proposition initiale, le système d’effet, et se contentera d’exposer une bibliothèque de domaines et quelques API de plus haut niveau, bâtis au-dessus de cette bibliothèque de domaine.

Le but est de découpler la partie runtime du développement d’OCaml multi-cœur du travail de conception sur le système d’effet qui requiert encore des efforts de conception.

Une prise en charge étendue de RISC-V à macOS/ARM64

Le compilateur OCaml gère deux modes de compilation : un mode bytecode qui fonctionne sur toute architecture où un compilateur C est disponible ; et un mode natif qui émet directement des binaires natifs. Ce mode natif est d’ailleurs le seul utilisateur du système objet d’OCaml au sein du compilateur lui-même.

Cette gestion native requiert de s’adapter aux nouvelles familles de processeurs et aux variations d’ABI suivant les systèmes d’exploitation. OCaml 4.11.0 a ainsi vu apparaître la prise en charge du RISC-V sous Linux. De manière similaire, la prise en charge pré-existante pour ARM64 a été étendue pour couvrir les conventions d’appels de macOS dans OCaml 4.12.0 .

De meilleurs messages d’erreurs

Écrire des messages d’erreurs utiles est une tâche plus difficile qu’il n’y paraît. Il peut être tentant de communiquer une erreur interne sur l’implémentation ou d’évoquer une théorie avec laquelle l’utilisateur n’est pas familier. Un autre problème assez fréquent pour les erreurs de types dans OCaml est que le vérificateur de type est optimisé pour vérifier rapidement que le code est bien typé. Avec ce mode de fonctionnement, on ne découvre parfois une erreur uniquement après qu’une série de petites erreurs nous ait mené à une situation impossible.

En bref, il reste pas mal de travail à faire pour améliorer les messages d’erreurs d’OCaml. Mais cette année 2021 a vu quelques progrès intéressants, et d’autres sont déjà intégrés ou en cours d’intégration dans la version de développement d’OCaml.

Des messages d’erreurs plus détaillés pour les foncteurs

Les foncteurs sont des fonctionnalités uniques d’OCaml. Ils permettent de décrire des modules qui dépendent d’autres modules. Par exemple, la définition d’un module Graphe peut prendre comme argument un module Sommet et un module Arete :

module Graphe(Sommet:SOMMET)(Arete:ARETE) = struct ... end

Je peux ensuite instancier ce foncteur avec diverses implémentations de ARETE et SOMMET.

Par exemple :

module Graphe_basique = Graphe(Sommet_basique)(Arete_basique)
module Graphe_colore = Graphe(Sommet_colore)(Arete_basique)

Cette formulation en termes de foncteur permet de décrire des algorithmes de graphes indépendamment de l’implémentation des arêtes ou sommets (sont-ils nommés ? colorés ?).

Avant OCaml 4.13, les erreurs liées à ces foncteurs pouvaient être très verbeuses. Par exemple, si j’applique le foncteur Graphe avec un argument en trop :

module G = Graphe(Etiquette)(Sommet)(Arete)

Le vérificateur de type d’OCaml se plaignait que le module Etiquette n’est pas un SOMMET, ce qui donne un message d’erreur qui ressemble à cela :

       Modules do not match:
         sig type t = string end
       is not included in
         SOMMET
       The value `label' is required but not provided
       The value `create' is required but not provided
       The type `label' is required but not provided
       The value `equal' is required but not provided
       The value `hash' is required but not provided
       The value `compare' is required but not provided

Avec OCaml 4.13.0, le vérificateur de type prend de la hauteur et essaye d’identifier des erreurs de haut niveau dans les erreurs liées aux foncteurs : est-ce que l’utilisateur n’aurait pas oublié un argument ? Ajouté un argument ? Modifié quelques arguments ?

       Error: The functor application is ill-typed.
       These arguments:
         Etiquette Sommet Arete
       do not match these parameters:
         functor (Sommet : SOMMET)(Arete : ARETE)} -> 
      1. The following extra argument is provided
        Etiquette : sig type t = string end
      2. Module Sommet matches the expected module type SOMMET
      3. Module Arete matches the expected module type ARETE

De plus en utilisant des méthodes de diffing (comparaison, généralement utilisées dans les correcteurs orthographiques ou du fuzzy searching/recherche floue), le vérificateur de type est capable de trouver une erreur la plus probable même dans des cas complexes.

Confusion entre module et module types

Un des détails surprenants d’OCaml est que beaucoup d’objets ont leur espace de noms séparé, ce qui mène parfois à des erreurs entêtantes. Par exemple :

module type M = sig type t end
type u = M.t

Error: Unbound module M

ce message en OCaml 4.10.0 semble s’obstiner à ne pas reconnaître l’existence de M.

Le véritable problème est que M n’est pas un module, et donc ne définit pas de types. Depuis la version 4.12.0, le message d’erreur reconnaît qu’il s’agit d’une confusion naturelle :

Error: Unbound module M
Hint: There is a module type named M, but module types are not modules

Une explication des problèmes de régularité

Parfois, les messages d’erreurs sont évidents pour leurs auteurs, et totalement obscurs sans le bon contexte.

C’était notamment le cas d’un des messages d’erreurs concernant les types récursifs non-réguliers. Si l’enchaînement des mots précédents ne vous parle pas, il y avait de grandes chances que ce message d’erreur vous laisse pantois :

   type ('a,'b) x = [ `X of ('b,'a) y ]
   and ('a,'b) y = [ `Y of ('a,'b) x ]

Error: In the definition of y, type ('b, 'a) x should be ('a, 'b) x

Il commet en effet trois péchés cardinaux pour un message d’erreur : il propose un correctif faux, il ne parle pas du code visible par l’utilisateur mais du résultat d’un calcul invisible du compilateur, et il ne pointe pas vers la source de l’erreur.

Ce souci est corrigé, et OCaml 4.12.0 prend désormais le temps d’expliquer le problème :

Error: This recursive type is not regular.
The type constructor x is defined as
type ('a, 'b) x
but it is used as
('b, 'a) x
after the following expansion(s):
('b, 'a) y = [ `Y of ('b, 'a) x ]
All uses need to match the definition for the recursive type to be regular.

Le message d’erreur est long. Cependant il explique non seulement la nature du problème (un type paramétré est utilisé de façon différente au sein d’un même groupe de définition récursif) mais aussi comment le vérificateur de type a découvert l’erreur.

Améliorations de l’expérience utilisateur

Il y a aussi beaucoup d’améliorations de taille plus modeste qui sont plus difficiles à catégoriser.
Parmi celles qui ont retenu mon attention sur ces deux dernières versions, je peux citer :

Statmemprof : profiler la mémoire sur des programmes en production.

Pour des langages à glaneur de cellules (GC) comme OCaml, l’allocation et la désallocation de mémoire est un axe à la fois important et assez invisible de la performance des programmes. Il peut donc être important de surveiller le travail du GC dans un programme pour évaluer des problèmes de performances, ou s’assurer qu’il n’y ait pas de fuite de mémoire dans un serveur tournant durant des années.

Dans les versions d’OCaml antérieures à 4.12, la bibliothèque Spacetime fournissait de tels outils de surveillance en continu de la mémoire.

Cependant analyser le travail du GC peut-être extrêmement coûteux aussi bien en termes de temps que d’espace. Et il était pratiquement impossible d’utiliser Spacetime dans un environnement de production à cause de ces coûts.

Statmemprof est une réponse à ces problématiques : il s’agit d’un outil de profilage statistique de l’allocation et de la désallocation de la mémoire. En s’autorisant à n’analyser qu’une partie des allocations et des désallocations, il devient possible de contrôler le coût de cette analyse de la mémoire et de la rendre négligeable. Intégrer cette analyse dans du code en production devient alors possible. On peut même s’autoriser à ajuster le comportement du programme en fonction de sa consommation mémoire actuelle.

Des noms pour les warnings

Après 25 ans d’existence, OCaml a accumulé plusieurs dizaines d’avertissements (70 dans la version 4.13.0). Fort heureusement, la configuration de ces avertissements est souvent laissée soit au compilateur soit au système d’assemblage. Notamment, dune, le système d’assemblage de prédilection de la plupart des paquets opam, a un choix d’avertissements assez strict par défaut.

Il reste néanmoins pratique de pouvoir modifier cette configuration pour un fichier ou une fonction spécifique. Par exemple, on peut activer le warning 27 pour juste la fonction f avec :

let f x = () [@@warning "+27"]

Cependant, à la lecture, il n’est pas exactement évident de se rappeler l’objet de cet avertissement 27. Cela d’autant plus lorsque l’avertissement est utilisé ponctuellement. La nouvelle mouture d’OCaml permet enfin de nommer ces avertissements :

let f x = () [@@warning "+unused-var-strict"]

Et la Stdlib s’agrandit

La bibliothèque standard voit arriver deux nouveaux modules liés aux threads :

  • Atomic : ce module est là pour préparer en douceur la compatibilité avec le runtime multi-cœur.

    • Thread.Semaphore : ce module offre une contrepartie au Mutex qui n’a pas besoin d’être verrouillé et déverrouillé dans le même fil d’exécution.

et un nouveau module de structure de données :

  • Either : il s’agit d’un module d’alternative générique (on a soit un Left a soit un Right b) qui est utile lorsque nommer explicitement les deux alternatives serait pénible.

Fut un temps, la bibliothèque standard d’OCaml avait pour objectif de rester assez minimaliste. Ce choix a engendré la création d’au moins quatre bibliothèques étendant la bibliothèque standard (extlib, batteries, base, containers). Cependant depuis, OCaml 4.07 la bibliothèque standard s’est ouverte à plus d’améliorations. Néanmoins, l’évolution de la bibliothèque standard reste basée sur un principe de quasi-unanimité, son rythme d’évolution reste donc très mesuré.

Des piles d’appels plus expressives

Lorsque qu’une fonction lève une exception qui n’est pas attrapée, la pile d’appel (backtrace) contient désormais des informations sur les noms des fonctions qui se sont retrouvées sur la pile d’appel. Par exemple exécuter :

   let () =
     let f () =
       let g () = raise Exit in
       fun () -> g ()
      in
      f () ()

nous informe que

Raised at Backtrace_example.f.g in file "backtrace_example.ml" (inlined), line 3, characters 16-26

plutôt que le laconique

Raised at file "backtrace_example.ml" (inlined), line 3, characters 16-26

Plus de types pour les utilisateurs experts

Le système s’est aussi enrichi de fonctionnalités plus orientées vers les auteurs de bibliothèques, et les utilisateurs experts.

Des noms pour les types existentiels

Les types existentiels sont une des fonctionnalités nouvelles apportées par les Types de Données Algébriques Généralisés (GADTs). Pour faire simple, il s’agit de types qui n’existent qu’à l’intérieur d’un constructeur.

Par exemple, je peux décrire une pipeline de transformation de 'a vers b en plusieurs étapes :

type ('entree,'sortie) pipeline =
  | Vide: ('entree,'entree) pipeline
  | Etape: ('entree,'intermediaire) pipeline * ('intermediaire -> 'sortie) 
             -> ('entree,'sortie) pipeline

Ici le constructeur Etape prend comme argument un pipeline de entree vers un type intermediaire, et une fonction de ce type intermediaire vers le type sortie et me donne en retour un pipeline de l’entrée vers la sortie.

Le point intéressant avec définition est que ce type intermédiaire n’est pas un type concret connu. Il s’agit d’un type inconnu dont je sais seulement qu’il est partagé par ma pipeline interne, et ma fonction de transformation.

Une bonne façon de voir comment ce type se comporte est d’implémenter une fonction envoyer qui applique toutes les étapes de la pipeline à une entrée et obtient une sortie.

 let rec envoyer: type entree sortie. (entree,sortie) pipeline -> entree -> sortie =
  fun pipeline entree ->
  match pipeline with
  | Vide -> entree
  | Etape(pipeline_interne, transformation_finale) ->
    entree |> envoyer pipeline_interne |> transformation_finale
    (* [x |> f] signifie [f x] *)

Ici, tout ce passe bien. Mais que se passe-t-il si j’essaye d’appliquer la transformation finale avant le reste de la pipeline ?

let rec envoyer_erronee: type entree sortie. (entree,sortie) pipeline -> entree -> sortie =
  fun pipeline entree ->
  match pipeline with
  | Vide -> entree
  | Etape(pipeline_interne, transformation_finale) ->
    entree |> transformation_finale |> envoyer pipeline_interne

J’obtiens une erreur de compilation qui se plaint que le type de entree n’est pas le bon :

Error: This expression has type entree but an expression was expected of type
$Etape_'intermediaire

Et en effet, le code est faux parce que le type entree ne correspond pas au type attendu par la transformation finale. Le nom du type attendu $Etape_'intermediaire est cependant assez complexe.

Il s’agit d’un nom automatiquement généré pour un type existentiel à partir de la définition de type et du constructeur qui l’a introduit. Ici le nom est assez clair, mais dans des cas complexes ces noms générés automatiquement peuvent être difficiles à déchiffrer. Une des nouveautés dans 4.13.0 est qu’il est désormais possible de nommer soi-même les types existentiels introduits dans le filtrage de motif:

let rec envoyer_erronee: type entree sortie. (entree,sortie) pipeline -> entree -> sortie =
  fun pipeline entree ->
  match pipeline with
  | Vide -> entree
  | Etape (type intermediaire)
      (pipeline_interne, transformation_finale:
        (entree, intermediaire) pipeline * (intermediaire -> sortie)
      ) ->
    entree |> transformation_finale |> envoyer pipeline_interne

Cette fois-ci, le message d’erreur utilise notre nom de type :

Error: This expression has type entree but an expression was expected of type
intermediaire

Ce qui devrait réduire légèrement le temps passé à faire compiler du code utilisant fortement les GADT. Cette notation permet aussi d’obtenir facilement le type abstrait correspondant au type existentiel pour lequel il y a des applications plus élaborées.

De l’injectivité pour vos types

Les bibliothèques vont pouvoir ajouter des points d’exclamation à leurs types

type !'a vec

pour indiquer que le paramètre 'a est vraiment utilisé dans le type et n’est pas un type fantôme.
Cela permet de débloquer certains usages avancés des GADT où il est vital de savoir si int vec et forcément différent de float vec.

Par exemple, avec cette annotation, le vérificateur de type sait qu’avec la définition suivante :

type _ int_or_float_vec =
| Int_vec : int vec -> int vec int_or_float_vec
| Float_vec: float vec -> float vec int_or_float_vec

lorsqu’on a une valeur de type 'a int_or_float_vec, la variable 'a est forcément soit int vec soit float vec. En d’autres mots, on ne peut jamais se procurer une valeur de type char int_or_float_vec :

let impossible: char int_or_float_vec -> _ = function _ -> .

Sans cette annotation, le vérificateur de type ne peut éliminer la possibilité que le type 'a vec ait été défini en tant que synonyme de char:

type 'a vec = char

Comme pour les annotations de variances, l’injectivité est automatiquement inférée pour les types non-abstraits. Ces annotations sont donc essentiellement là pour les auteurs de bibliothèque.

Au-delà d’OCaml multi-cœur

Si l’implémentation d’OCaml multi-cœur se rapproche lentement mais inexorablement, les plans pour le futur d’OCaml ne s’arrêtent pas là.

En particulier, la gestion de la concurrence et du parallélisme sera à terme basée sur un système d’effets. La prochaine étape de ce côté sera de concevoir et déployer un système d’effets typés facile à utiliser en pratique.

Mais le développement d’OCaml 5 ne se concentrera pas uniquement sur l’aspect multi-cœur. Une des forces d’OCaml est son système de modules à la fois expressif et adapté à la compilation séparée. Cependant, cette puissance a un prix, et les usages avancés du système de modules peuvent être particulièrement lourds syntaxiquement. Un des projets en cours pour OCaml 5 est d’introduire des méthodes plus légères pour décrire des fonctions paramétrées par des modules, à travers un système de foncteurs légers et implicites.

Aller plus loin

  • # Bytecode VS natif

    Posté par  (site web personnel) . Évalué à 5.

    Le compilateur OCaml gère deux modes de compilation : un mode bytecode qui fonctionne sur toute architecture où un compilateur C est disponible ; et un mode natif qui émet directement des binaires natifs.

    Hum je ne suis pas sûr d'avoir bien compris cette phrase. Tel que je l'avais compris ces dernières années, le compilateur pouvait :

    • soit générer un bytecode destiné à une VM spécifique (le code est portable, il "suffit" que la VM ait été portée sur la plateforme, comme d'habitude)
    • soit générer du C, qui est alors passé à un compilateur C afin de générer un exécutable natif, l'idée étant d'avoir un compilateur ciblant beaucoup de plateformes avec relativement peu d'effort (l'équipe derrière OCaml étant plutôt petite). J'imagine que faire un frontend GCC demande plus d'efforts que pondre du C (et LLVM est plus récent—j'ai entendu parlé d'un projet de frontend OCaml pour ce dernier mais je ne sais pas où ça en est).

    Le bout de phrase "un mode bytecode qui fonctionne sur toute architecture où un compilateur C est disponible" c'est parce que la VM est écrite en C très portable ? C'est une erreur ? N'ai-je rien compris ?

    Glaneur de Cellule (GC)

    C'est la première fois que je vois cette traduction, j'aime beaucoup.

    • [^] # Re: Bytecode VS natif

      Posté par  (site web personnel) . Évalué à 5.

      soit générer un bytecode destiné à une VM spécifique (le code est portable, il "suffit" que la VM ait été portée sur la plateforme, comme d'habitude)

      • Le compilateur ocamlc génère effectivement du bytecode pour une VM qui est effectivement écrite en C, et donc portable sur n'importe quelle plateforme. Il a même été écrit une VM sur PIC18F
      • Il existe effectivement un vieux projet qui génère du C à partir du bytecode, d'ailleurs du même auteur qu'Ocapic

      De ce que j'ai compris, générer du C à partir d'OCaml est compliqué de par des problèmes de différences de sémantiques entre le OCaml et le C. Mais d'autres ici t'expliqueront ça surement bien mieux que moi.

      Il a effectivement existé des tentatives d'écrire un backend LLVM, mais les derniers travaux datent d'il y a presque 10 ans. Pourquoi cela n'a pas été continué ?
      Dommage, Haskell l'utilise et cela permettrait certainement des optimisations et un fort accroissement d'architectures utilisables.

      « Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker

      • [^] # backends LLVM

        Posté par  . Évalué à 5.

        Pour Haskell le backend LLVM n'est toujours pas utilisé par défaut. Il est intégré upstream et maintenu, mais peu de gens travaillent dessus et il ne semble pas avoir remplacé le backend principal pour la plupart des utilisateurs (hors quelques cas spécifiques de calcul numérique sur des tableaux).

        Quelques pointeurs
        - GHC wiki: improving the LLVM backend
        - Opinion piece on GHC backends

        (Côté Rust aussi on parle de back-ends alternatifs, par exemple Cranelift qui vit maintenant au sein de la communauté WebAssembly. Le futur de LLVM comme "backend universel" n'est pas évident.)

    • [^] # Re: Bytecode VS natif

      Posté par  . Évalué à 8.

      Le compilateur natif génère de l'assembleur directement et non pas du C. Cela demande plus d'effort mais cela permet d'ajuster plus finement l'assembleur généré pour les spécificités d'OCaml. Par exemple les conventions d'appels de fonctions sont différentes. Un des avantages est qu'OCaml réserve un registre pour soit le dernier gestionnaire d'exception soit l'état du domaine dans les versions récentes, ce qui permet d’accélérer le traitement des exceptions.

      La taille des générateurs d'assembleur spécifique à chaque architecture reste modeste, autour de 12 000 lignes de code au total pour supporter x86, amd64, arm, arm64, power, riscV, s390x. Mais effectivement les instructions plus spécifiques à une famille de processeurs (notamment les instructions vectorielles des processeurs x86-64) ne sont pas utilisés.

      Utiliser LLVM comme backend est parfois considéré, mais ce n'est pas forcément idéal. D'une part, il y a le problème du temps de compilation qui affecte Rust parce que le compilateur Rust émet des formes intermédiaires LLVM de mauvaise qualité, que LLVM met beaucoup de temps à compiler. D'autre part, se reposer sur les passes d'optimisations de LLVM, c'est un peu espérer que les passes d'optimisations génériques ou penser pour le C et le C++ vont optimiser du code plus fonctionnel un peu par accident. Par contre, il est clair que LLVM fait plus d'optimisations de bas niveau que le compilateur OCaml. Il existe d'ailleurs Duplo qui est un cadriciel pour faire de l'optimisation post-link de programme OCaml en utilisant LLVM. (Il me semble qu'il y avait aussi la question du GC qui n'était pas si clair mais je ne sais pas si c'est toujours d'actualité).

      • [^] # Re: Bytecode VS natif

        Posté par  (site web personnel) . Évalué à 3.

        Merci pour ces explications !

        D'une part, il y a le problème du temps de compilation qui affecte Rust parce que le compilateur Rust émet des formes intermédiaires LLVM de mauvaise qualité, que LLVM met beaucoup de temps à compiler.

        rustc a introduit il y a quelques années une représentation intermédiaire (appelée MIR—voir cet article par exemple) qui permet de faire "facilement" des optimisations avec la sémantique de Rust que LLVM ne pouvait pas faire (ou difficilement) et qui a permis de gagner en temps de compilation (entre autres choses), même si ça sera toujours perfectible. LLVM est quand même pas mal tourné vers C/C++ (pour des raisons évidentes), mais au final il y a tellement de gens qui travaillent dessus que faire une solution ad-hoc qui fasse mieux est loin d'être évident. Il y a aussi un travail en cours pour un frontend Rust à GCC ; je ne sais pas si/quand ça aboutira, mais je ne serais pas étonné qu'il y ait le même genre d'écueils.

      • [^] # Re: Bytecode VS natif

        Posté par  (site web personnel) . Évalué à 6.

        D'autre part, se reposer sur les passes d'optimisations de LLVM, c'est un peu espérer que les passes d'optimisations génériques ou penser pour le C et le C++ vont optimiser du code plus fonctionnel un peu par accident.

        A l'époque de Lisaac qui avait été en tête du langage shootout, le compilo C rajoutait une couche d'optimisation. Lisaac produisait toujours du C. Un code sans optim ou avec optim était accélérer en moyenne de +15% par le compilateur C.

        Il y a des optim très difficile à faire au niveau du C (transformer les appels virtuels en simple call) et d'autres bas niveau très difficile à faire à haut niveau (scheduling d'instructions)

        "La première sécurité est la liberté"

    • [^] # Commentaire supprimé

      Posté par  . Évalué à 3.

      Ce commentaire a été supprimé par l’équipe de modération.

      • [^] # Re: Bytecode VS natif

        Posté par  . Évalué à 2. Dernière modification le 26 septembre 2021 à 19:20.

        Tout à fait d'accord, j'ai d'ailleurs dû aller sur wikipe pour vérifier qu'il s'agissait bien de ce que son acronyme suggérait, mais pas son nom.
        Ramasse-miette est très bien et suggestif [1] , quel est son problème ? Ça n'est pas assez inclusif ? ;-)

        S'il faut coller aux initiales (et pourquoi le faudrait-il absolument, même si c'est un plus ?) [2] , pourquoi pas Gros Collecteur ? Grand Crématoire ? Gigoter et Compacter ? Gary Cooper ?

        Ceci dit, c'est un détail, merci pour cette dépêche extrêmement intéressante. J'ai toujours beaucoup aimé l'élégance et la cohérence théorique de ce langage.


        [1] Ça me fait penser au truc à roulette qu'ils passaient sur la nappe dans les grands restos, ça suggère vraiment l'analogie avec le rôle du G.C.
        [2] AIDS -> SIDA, NATO -> OTAN, CCCP -> URSS, etc.

      • [^] # Re: Bytecode VS natif

        Posté par  . Évalué à 3.

        Glaner fait aussi écho à la nature non-déterministe de la collection. J'avoue que toutes les traductions françaises sonnent pittoresques à mon oreille. Même en anglais, j'ai plus tendance à penser GC que garbage collector, le rétroacronyme me semblait donc potentiellement intéressant.

        • [^] # Re: Bytecode VS natif

          Posté par  (site web personnel, Mastodon) . Évalué à 2.

          La/le collecte/collecteur pas la/le collection/collectionneur
          Glaner peut par contre donner une impression d'aléatoire alors que ce n'est pas le cas…

          “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: Bytecode VS natif

        Posté par  . Évalué à 6.

        C'est parce que le terme cellule mémoire se perd un peu. Ce qui le rendait bien plus pertinent que ramasse miettes.

        https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Bytecode VS natif

      Posté par  (site web personnel) . Évalué à 4.

      Glaneur de Cellule (GC)
      C'est la première fois que je vois cette traduction, j'aime beaucoup.

      C'est une traduction standard qui, comme tu l'observes évidemment, préserve l'acronyme.
      En revanche, ça devrait être "glaneur de cellules" au pluriel.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.