
la nouvelle version est livrée avec de nouvelles fonctionnalités qui vont améliorer les performances du langage
Julia est un langage de programmation de haut niveau, performant et dynamique pour le calcul scientifique, avec une syntaxe familière aux utilisateurs d'autres environnements de développement similaires (Matlab, R, Scilab, Python ...). Il fournit un compilateur sophistiqué, un système de types dynamiques avec polymorphisme paramétré, une exécution parallèle distribuée, des appels directs de fonctions C, Fortran et Python. Julia 1.9, la dernière version du langage de programmation dynamique de haut niveau pour le calcul technique, est sortie après trois bêtas et trois releases candidates. La nouvelle version est livrée avec plusieurs nouvelles fonctionnalités qui devraient améliorer les performances et la convivialité du langage. L'une des mises à niveau les plus importantes de Julia 1.9 est la mise en cache du code natif, qui devrait améliorer considérablement les performances du langage. La nouvelle version comprend également des extensions de package, un heap snapshot et un indice d'utilisation de la mémoire pour le GC avec --heap-size-hint, qui devraient rendre le langage plus convivial et efficace. Les performances de tri de Julia ont également été améliorées dans la version 1.9, ce qui devrait permettre aux utilisateurs de trier plus facilement et rapidement de grands ensembles de données.
Dans Julia, la précompilation consiste à compiler le code du paquetage et à enregistrer la sortie compilée sur le disque sous la forme d'un "fichier cache" pour chaque paquetage. Ce processus réduit considérablement le temps nécessaire à la compilation lors de l'utilisation du paquet, car vous n'avez besoin de le construire qu'une seule fois et vous pouvez le réutiliser plusieurs fois.
Cependant, avant Julia 1.9, seule une partie du code compilé pouvait être sauvegardée : les types, les variables et les méthodes étaient sauvegardés, ainsi que les résultats de toute inférence de type pour les types d'arguments spécifiquement précompilés par les développeurs du paquetage. Le code natif, c'est-à-dire le code qui s'exécute réellement sur votre processeur, est notablement absent des fichiers de cache. Bien que la mise en cache ait aidé à réduire la latence TTFX (Time-to-first-execution), la nécessité de régénérer le code natif à chaque session signifiait que de nombreux packages souffraient encore de longues latences TTFX.
Avec l'introduction de Julia 1.9, la mise en cache du code natif est désormais disponible, ce qui se traduit par une amélioration significative de la latence TTFX et ouvre la voie à de futures améliorations dans l'ensemble de l'écosystème. Les auteurs de paquets peuvent désormais utiliser des instructions de précompilation ou des charges de travail avec PrecompileTools pour mettre en cache les routines importantes à l'avance. Les utilisateurs peuvent également créer des paquets locaux "Startup" personnalisés qui chargent les dépendances et précompilent les charges de travail adaptées à leur travail quotidien.
Cette fonctionnalité s'accompagne de certains compromis, tels que l'augmentation du temps de précompilation de 10 à 50 %. Toutefois, comme il s'agit d'un coût unique, le jeu en vaut la chandelle. Les fichiers de cache sont également devenus plus volumineux en raison du stockage d'un plus grand nombre de données et de l'utilisation d'un format de sérialisation différent.
Le graphique ci-dessous illustre les changements dans le temps de chargement (TTL), le TTFX et la taille des fichiers de cache à partir de Julia 1.7 (avant toute amélioration récente de la précompilation) :
(Pour la plupart des paquets, TTFX est passé du statut de facteur dominant à celui de facteur pratiquement négligeable. Le TTL a également été réduit, mais pas de manière aussi spectaculaire que le TTFX. Les mêmes données sont présentées dans le tableau ci-dessous, les colonnes "ratio" représentant le rapport Julia 1.7 / Julia 1.9 et "total" signifiant "TTL + TTFX".
Ces chiffres révèlent une énorme amélioration de la qualité de vie dans une large gamme de paquets.
Avec PrecompileTools.jl, Julia 1.9 offre de nombreux avantages de PackageCompiler sans nécessiter de personnalisation de la part de l'utilisateur. Voici une comparaison explicite :
La différence de TTL s'explique par le fait que l'image système peut ignorer toutes les vérifications de validation du code qui sont nécessaires lors du chargement des paquets.
Au moment de la sortie de Julia 1.9, seule une petite partie de l'écosystème des paquets a adopté PrecompileTools. Au fur et à mesure que l'utilisation de ces nouveaux outils se généralise, les utilisateurs peuvent s'attendre à des améliorations continues de TTFX.
Méthodologie
Une charge de travail de démonstration a été conçue pour chaque paquetage. Cette charge de travail a été placée dans un paquet Startup et précompilée ; pour l'analyse comparative, nous chargeons le paquet Startup et exécutons la même charge de travail. Tous les détails peuvent être trouvés dans ce référentiel.
Extensions de paquets
Dans Julia, la puissance de la distribution multiple permet d'étendre les fonctionnalités à un large éventail de types. Par exemple, un package de traçage peut vouloir fournir des fonctionnalités pour tracer une grande variété d'objets Julia, dont beaucoup sont définis dans des packages séparés au sein de l'écosystème Julia. De plus, il est possible d'ajouter des versions optimisées de fonctions génériques pour des types spécifiques, tels que StaticArray, où la taille du tableau est connue au moment de la compilation, ce qui conduit à des améliorations significatives des performances.
Pour étendre une méthode à un type, il faut généralement importer le paquetage contenant le type, charger le paquetage pour accéder au type, puis définir la méthode étendue. Si l'on prend le cas d'utilisation du package "plotting", cela pourrait ressembler à ce qui suit :
Code : | Sélectionner tout |
1 2 3 4 5 | import Contours function plot(contour::Countours.Contour) ... end |
Julia 1.9 introduit les "extensions de paquets", une fonctionnalité qui, au sens large, charge automatiquement un module lorsqu'un ensemble de paquets est chargé. Le module, contenu dans un fichier dans le répertoire ext du paquetage parent, charge la "dépendance faible" et étend les méthodes. L'objectif est de ne pas avoir à payer pour des fonctionnalités que l'on n'utilise pas. Les extensions de paquetages offrent des fonctionnalités similaires à celles que Requires.jl propose déjà, mais avec des avantages clés, tels que la précompilation du code conditionnel et l'ajout de contraintes de compatibilité sur les dépendances faibles. La fonctionnalité d'extension de paquetage étant désormais de "première classe", les auteurs de paquetages devraient être moins réticents à commencer à l'utiliser qu'avec Requires.jl. Les extensions de paquetages peuvent être introduites d'une manière totalement rétrocompatible où l'on peut choisir d'utiliser Requires.jl ou de faire de la dépendance faible une dépendance normale sur les versions antérieures de Julia.
Comme exemple concret où les extensions de paquetage sont utilisées à bon escient, le paquetage ForwardDiff.jl fournit des routines optimisées pour la différenciation automatique lorsque l'entrée est un StaticArray. Dans Julia 1.8, il chargeait inconditionnellement le package StaticArrays, alors que dans 1.9, il utilise une extension de package. Il en résulte une amélioration significative du temps de chargement :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | # 1.8 (StaticArrays unconditionally loaded) julia> @time using ForwardDiff 0.590685 seconds (2.76 M allocations: 201.567 MiB) # 1.9 (StaticArrays not loaded) julia> @time using ForwardDiff 0.247568 seconds (220.93 k allocations: 13.793 MiB) |
Êtes-vous curieux de savoir comment votre mémoire est utilisée dans vos programmes Julia ? Avec l'introduction de Julia 1.9, vous pouvez désormais générer des Heap snapshot qui peuvent être examinés à l'aide de Chrome DevTools.
Pour créer un instantané du tas, il suffit d'utiliser le package Profile et d'appeler la fonction take_heap_snapshot, comme indiqué ci-dessous :
Code : | Sélectionner tout |
1 2 | using Profile Profile.take_heap_snapshot("Snapshot.heapsnapshot") |
Code : | Sélectionner tout |
Profile.take_heap_snapshot("Snapshot.heapsnapshot", all_one=true)
Indication d'utilisation de la mémoire pour le GC avec --heap-size-hint
Julia 1.9 introduit un nouveau drapeau de commande, --heap-size-hint=<size>, qui permet aux utilisateurs de fixer une limite d'utilisation de la mémoire, au-delà de laquelle le garbage collector (GC) travaillera plus agressivement pour nettoyer la mémoire inutilisée.
En spécifiant une limite de mémoire, les utilisateurs peuvent s'assurer que le garbage collector gère les ressources mémoire de manière plus proactive, réduisant ainsi le risque de manquer de mémoire.
Pour utiliser cette nouvelle fonctionnalité, il suffit de lancer Julia avec l'option --heap-size-hint suivie de la limite de mémoire souhaitée :
Code : | Sélectionner tout |
julia --heap-size-hint=<size>
Cette amélioration dans Julia 1.9 rend plus facile que jamais la gestion efficace des ressources mémoire, fournissant aux utilisateurs un meilleur contrôle et une plus grande flexibilité lorsqu'ils travaillent avec des applications gourmandes en mémoire.
Cette fonctionnalité a été introduite dans #45369.
Trier les performances
L'algorithme de tri par défaut a été remplacé par un algorithme de tri plus adaptatif qui est toujours stable et dont les performances sont souvent à la pointe de la technologie. Pour les types et ordres simples - BitInteger, IEEEFloat et Char triés dans l'ordre par défaut ou dans l'ordre inverse - nous utilisons un tri radix dont le temps d'exécution est linéaire par rapport à la taille de l'entrée. Cet effet est particulièrement prononcé pour Float16s qui a bénéficié d'une accélération de 3x-50x par rapport à la version 1.8.
Pour les autres types, l'algorithme de tri par défaut a été remplacé par l'algorithme interne ScratchQuickSort dans la plupart des cas, qui est stable et généralement plus rapide que QuickSort, bien qu'il alloue de la mémoire. Dans les situations où l'efficacité de la mémoire est cruciale, vous pouvez ignorer ces nouvelles valeurs par défaut en spécifiant alg=QuickSort.
Pour en savoir plus sur ces changements, vous pouvez regarder la conférence JuliaCon 2022, Julia's latest in high performance sorting et sa suite à venir dans JuliaCon 2023.
Les tâches et le pool de discussion interactif
Avant la version 1.9, Julia traitait toutes les tâches de la même manière, les exécutant sur tous les fils d'exécution disponibles sans distinction de priorité. Cependant, dans certaines situations, vous pouvez souhaiter que certaines tâches soient prioritaires, par exemple lors de l'exécution d'un heartbeat, de la fourniture d'une interface interactive ou de l'affichage d'une mise à jour de la progression.
Pour répondre à ce besoin, vous pouvez désormais désigner une tâche comme interactive lorsque vous la Threads.@spawn :
Code : | Sélectionner tout |
1 2 | using Base.Threads @spawn :interactive f() |
Code : | Sélectionner tout |
julia --threads 3,1
Pour plus d'informations, reportez-vous à la section du manuel sur le multithreading. Cette fonctionnalité a été introduite dans #42302 .
REPL
Module contextuel REPL
Dans Julia, la REPL évalue par défaut les expressions à l'intérieur du module "Main". À partir de la version 1.9, vous pouvez maintenant changer cela pour n'importe quel autre module. De nombreuses méthodes d'introspection, telles que varinfo, qui auparavant examinaient par défaut le module Main, seront désormais évaluées par défaut dans le module contextuel actuel de la REPL.
Cette fonctionnalité peut être particulièrement utile lors du développement d'un paquetage, car vous pouvez définir le paquetage comme module contextuel courant. Pour changer de module, il suffit d'entrer le nom du module dans la REPL et d'exécuter Meta+M (souvent Alt+M), ou d'utiliser la commande REPL.activate.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | julia> @__MODULE__ # Shows module where macro is expanded Main # Typing Base.Math and pressing Meta-m changes the context module (Base.Math) julia> @__MODULE__ Base.Math (Base.Math) julia> varinfo() name size summary ––––––––––– ––––––– ––––––––––––––––––––––––––––––––––––––––––––– @evalpoly 0 bytes @evalpoly (macro with 1 method) Math Module ^ 0 bytes ^ (generic function with 68 methods) acos 0 bytes acos (generic function with 12 methods) acosd 0 bytes acosd (generic function with 1 method) acosh 0 bytes acosh (generic function with 12 methods) acot 0 bytes acot (generic function with 4 methods) ... |
S'inspirant fortement du shell IPython (et d'autres systèmes basés sur des carnets de notes comme Mathematica), le REPL de Julia peut activer une "invite numérotée" qui stocke les objets évalués dans le REPL pour une utilisation ultérieure et garde une trace du nombre d'expressions qui ont été évaluées.
Pouvoir se référer à un objet évalué plus tôt peut être utile si, par exemple, on oublie de stocker le résultat d'un long calcul dans une variable et que l'on exécute ensuite autre chose (de sorte que le résultat est écrasé).
DelimitedFiles - première stdlib à être mise à jour
Julia est livrée avec un ensemble de bibliothèques standard ("stdlibs"

Cependant, cette approche a quelques inconvénients :
- les versions des stdlib sont liées à la version de Julia, ce qui oblige les utilisateurs à attendre la prochaine version de Julia pour recevoir les corrections de bogues ;
- l'intégration des stdlibs dans la sysimage entraîne un coût pour les utilisateurs qui ne les utilisent pas, car elles sont chargées à chaque démarrage de Julia ;
- le développement de stdlibs qui sont dans la sysimage peut être ennuyeux.
Dans la version 1.9, nous expérimentons un nouveau concept de "stdlibs upgradables" qui sont livrés avec Julia mais qui peuvent aussi être mis à jour comme des paquets normaux. Pour commencer, ceci est fait avec la petite stdlib DelimitedFiles, relativement peu utilisée.
En commençant par une nouvelle installation de Julia, nous pouvons voir que le paquet DelimitedFiles est chargeable et qu'il est chargé à partir de l'installation de Julia :
Code : | Sélectionner tout |
1 2 3 4 5 | julia> using DelimitedFiles [ Info: Precompiling DelimitedFiles [8bb1440f-4735-579b-a4ab-409b98df4dab] julia> pkgdir(DelimitedFiles) "/Users/kc/julia/share/julia/stdlib/v1.9/DelimitedFiles" |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | (@v1.9) pkg> add DelimitedFiles Resolving package versions... Updating `~/.julia/environments/v1.9/Project.toml` [8bb1440f] + DelimitedFiles v1.9.1 Updating `~/.julia/environments/v1.9/Manifest.toml` [8bb1440f] + DelimitedFiles v1.9.1 Precompiling environment... 1 dependency successfully precompiled in 1 seconds. 59 already precompiled. julia> using DelimitedFiles julia> pkgdir(DelimitedFiles) "/Users/kristoffercarlsson/.julia/packages/DelimitedFiles/aGcsu" |
--math-mode=fast est maintenant un no-op (#41638). L'équipe est arrivée à la conclusion qu'une option fastmath globale est impossible à utiliser correctement dans Julia. Par exemple, cela peut conduire à des surprises telles que exp retournant complètement la mauvaise valeur comme rapporté dans #41592. La combinaison d'une option fastmath à l'exécution avec la précompilation et la propagation des constantes conduit à des incohérences à moins que nous acceptions le coût d'une image système entièrement séparée.
Les utilisateurs sont encouragés à utiliser la macro @fastmath à la place, qui limite les effets de fastmath à un petit morceau de code.
Pkg
pkg> up Foo essaiera maintenant de mettre à jour uniquement Foo
Auparavant, il n'était pas précisé quels packages étaient réellement autorisés à être mis à jour lorsqu'on donnait un package spécifique à mettre à jour (pkg> up Foo). Maintenant up Foo n'autorise que Foo lui-même à se mettre à jour avec tous les autres packages ayant leur version corrigée dans le processus de résolution. Il est possible d'assouplir cette restriction avec les différentes options de la commande --preserve pour permettre par exemple aux dépendances de Foo de se mettre à jour. Voir la documentation de Pkg.update pour plus d'informations.
pkg> add ne mettra à jour automatiquement le registre qu'une fois par jour.
Pkg se souviendra désormais de la dernière fois que le registre a été mis à jour à travers les sessions de julia et n'effectuera une mise à jour automatique qu'une fois par jour lors de l'utilisation d'une commande add. Auparavant, le registre se mettait à jour automatiquement une fois par session. Notez que la commande update essaiera toujours de mettre à jour le registre.
pkg> add peut maintenant essayer d'ajouter uniquement des packages déjà installés
Lorsque l'on travaille avec de nombreux environnements, par exemple à travers les ordinateurs portables Pluto, le comportement par défaut de Pkg.add d'ajouter la dernière version du paquetage demandé et toutes les nouvelles dépendances peut signifier l'utilisation fréquente de la précompilation.
Il est maintenant possible de dire à Pkg.add de préférer ajouter des versions déjà installées de paquetages (ceux qui ont déjà été téléchargés sur votre machine), qui sont plus susceptibles d'être précompilés.
Pour accepter globalement cette nouvelle préférence, mettez la variable env JULIA_PKG_PRESERVE_TIERED_INSTALLED à true.
Ou pour permettre l'utilisation d'opérations spécifiques :
- pkg> add --preserve=tiered_installed Foo pour essayer cette nouvelle stratégie en premier dans la préservation à plusieurs niveaux;
- pkg> add --preserve=installed Foo pour essayer strictement cette stratégie, ou erreur.
Notez qu'en utilisant cette méthode, vous risquez d'installer de vieilles versions de packages, donc si vous rencontrez des problèmes, c'est généralement une bonne idée de faire une mise à jour vers la dernière version pour voir si le problème a été corrigé.
pkg> why pour vous dire pourquoi un paquetage est dans le manifeste
Pour montrer la raison pour laquelle un package est dans le manifeste, une nouvelle commande pkg> why Foo est disponible. La sortie est toutes les différentes façons d'atteindre le paquetage à travers le graphe de dépendance en commençant par les dépendances directes.
Code : | Sélectionner tout |
1 2 3 4 | (jl_zMxmBY) pkg> why DataAPI CSV → PooledArrays → DataAPI CSV → Tables → DataAPI CSV → WeakRefStrings → DataAPI |
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.