La version 1.6 du langage Julia est disponible, elle apporte une réduction de la latence des compilateurs
et supprime les recompilations inutiles

Les responsables du langage Julia ont annoncé la disponibilité de Julia 1.6 le 24 mars. Elle est présentée comme étant une exception, car elle est susceptible de devenir la prochaine version de support à long terme (LTS) de Julia. De plus, la version a été testée pour les régressions par rapport à tous les paquets open source enregistrés et les problèmes découverts ont été corrigés. La décision finale quant à savoir si Julia 1.6 deviendra la prochaine version de support à long terme sera prise après qu'elle ait été testée par les développeurs.

Julia est un langage de programmation conçu par des chercheurs du MIT en 2009 et dévoilé pour la première fois au grand public en 2012. C’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. Julia connaît une croissance fulgurante depuis sa sortie et certains vont même jusqu’à dire qu’il s’agit du langage idéal pour le calcul scientifique, la science des données et les projets de recherche. Le langage s'est popularisé lorsque le projet est devenu open source en 2012. Il est actuellement disponible sous licence MIT.

À la base, ses concepteurs voulaient un langage avec une licence libre et renfermant de nombreux avantages surtout pour la communauté scientifique. « Nous voulons un langage open source, avec une licence libre. Nous voulons un langage qui associe la rapidité de C et le dynamisme de Ruby. En fait, nous voulons un langage homoïconique, avec de vraies macros comme Lisp et avec une notation mathématique évidente et familière comme MATLAB. Nous voulons quelque chose d’aussi utilisable pour la programmation générale que Python, aussi facile pour les statistiques que R, aussi naturel pour la gestion de chaîne de caractères que Perl, aussi puissant pour l’algèbre linéaire que Matlab et aussi bien pour lier des programmes que le shell. Nous voulons qu’il soit à la fois interactif et compilé », avaient-ils déclaré en 2012.

La plupart des versions de Julia sont programmées dans le temps et ne sont donc pas planifiées autour de fonctionnalités spécifiques, mais selon les responsables du projet Julia, la version 1.6 est une exception, car elle est susceptible de devenir la prochaine version LTS de Julia. Pour cette raison, ils ont pris plus de temps pour développer la version afin de s'assurer que les fonctionnalités qui sont nécessaires pour l’évolution de l'écosystème soient incluses dans la version. De plus, la version a été testée pour les régressions par rapport à tous les paquets open source enregistrés et les problèmes ont été recherchés et corrigés. Voici, ci-dessous, quelques-unes des améliorations et fonctionnalités apportées par la version 1.6 de Julia.

Précompilation parallèle

L'exécution de toutes les instructions d'un module implique souvent la compilation d'une grande quantité de code, Julia crée des caches précompilés du module pour réduire ce temps. Dans la version 1.6, cette précompilation du module est plus rapide et se produit avant que la fin du mode pkg>. Jusqu'à présent, la précompilation se déroulait uniquement sous la forme d’une séquence de traitement unique, précompilant les dépendances une par une lorsque cela était nécessaire pendant le processus de chargement du code linéaire lorsqu'un paquet est utilisé ou importé pour la première fois. Par le passé, avec la version 1.5 et inférieur, si on prend l’exemple des équations différentielles, avec DifferentialEquations.jl.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
(v1.5) pkg> add DifferentialEquations
...
julia> @time using DifferentialEquations
[ Info: Precompiling DifferentialEquations [0c46a032-eb83-5123-abaf-570d42b7fbaa]
  474.288251 seconds …
Dans la version 1.6, le mode pkg> bénéficie d'une opération de précompilation parallèle qui est invoquée automatiquement après les actions sur les paquets, afin de garder l'environnement actif prêt à être chargé.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
(v1.6) pkg> add DifferentialEquations
...
Precompiling project...
  Progress [========================================>]  112/112
112 dependencies successfully precompiled in 72 seconds
 
julia> @time using DifferentialEquations
  4.995477 seconds …
Alors que le processus précédent de précompilation du chargement du code prenait environ 8 minutes pour précompiler et charger DifferentialEquations sans indiquer la progression pendant ce processus, le nouveau mécanisme prend un peu plus d'une minute pour précompiler tout en indiquant la progression à travers les dépendances. Ensuite, la première fois que le paquet est chargé, il se charge à pleine vitesse. Le nouveau processus de précompilation parallèle adopte une approche en profondeur qui consiste à travailler sur l'arbre des dépendances du manifeste, en commençant par précompiler les paquets sans dépendances et en remontant jusqu'aux paquets répertoriés. Étant donné que l'opération est multi-processus et non multi-thread, elle n'est donc pas limitée par le nombre de threads de Julia.

Les erreurs pendant la précompilation seront affichées seulement pour les paquets énumérés dans le projet, afin d'éviter de précompiler des dépendances qui sont simplement citées et non utilisées dans l'environnement. Le processus de précompilation automatique se souviendra s'il a échoué pour un paquet dans l'environnement donné et ne réessayera pas jusqu'à ce qu'il change. La précompilation automatique peut être gracieusement interrompue avec ctrl-c.

Temps de compilation

Selon les concepteurs de Julia, il s’agit d’un petit changement qui devrait aider les nouveaux venus à comprendre l'une des bizarreries de Julia. Désormais, la macro de synchronisation @time et la version détaillée @timev indiquent désormais si une partie du temps rapporté a été consacrée à la compilation.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
julia> x = rand(10,10);
 
julia> @time x * x;
  0.540600 seconds (2.35 M allocations: 126.526 MiB, 4.43% gc time, 99.94% compilation time)
 
julia> @time x * x;
  0.000010 seconds (1 allocation: 896 bytes)
Compte tenu de la compilation Just In Time (JIT) ou Just Ahead Of Time (JAOT) de Julia, la première fois que le code est exécuté, l'effort de compilation est souvent substantiel, avec des améliorations de vitesse importantes observées dans les appels suivants. Ce changement met en évidence ce comportement, servant à la fois de rappel et d'outil pour éliminer les efforts de compilation non désirés.

Notons que dans certains cas, le système regarde à l'intérieur de l'expression @time et compile une partie du code appelé avant que l'exécution de l'expression de niveau supérieur ne commence. Lorsque cela se produit, une partie du temps de compilation ne sera pas comptabilisée. Pour inclure ce temps, on peut lancer @time @eval.

Suppression des recompilations inutiles

Une des caractéristiques les plus puissantes de Julia est son extensibilité : on peut ajouter de nouvelles méthodes à des fonctions précédemment définies, et utiliser des méthodes précédemment définies sur de nouveaux types. Parfois, ces nouvelles entités obligent Julia à recompiler le code pour tenir compte des changements dans la version. Cela se produit en deux étapes : d'abord, le code « obsolète » est invalidé, le marquant comme impropre à l'utilisation ; ensuite, si nécessaire, le code est à nouveau compilé à partir de zéro en tenant compte des nouvelles méthodes et des nouveaux types.

Les versions précédentes de Julia étaient quelque peu conservatrices, et invalidaient l'ancien code dans certaines circonstances où il n'y avait pas de changement réel dans la version. De plus, il y avait de nombreux endroits où Julia et ses bibliothèques standards étaient écrites d'une manière qui ne permettait pas l'inférence de type de Julia. Étant donné que le compilateur devait parfois invalider du code juste parce qu'une nouvelle méthode pouvait s'appliquer, toute incertitude sur les types amplifie le risque et la fréquence d'invalidation. Dans les anciennes versions de Julia, la combinaison de ces effets rendait l'invalidation très répandue : le simple chargement de certains paquets entraînait l'invalidation de 10 % du code précompilé de Julia. Le délai de recompilation pouvait parfois rendre les sessions interactives plus lentes.

Dans la version 1.6, le schéma d'invalidation de l'ancien code a été rendu plus précis et sélectif. De plus, Julia et ses bibliothèques standards ont fait l’objet d’une refonte complète pour aider l'inférence de type à arriver plus souvent à une réponse concrète. Le résultat est un langage Julia plus léger, plus rapide et considérablement plus réactif et agile dans les sessions interactives, surtout grâce à une invalidation de méthodes plus précise.

Réduction de la latence à la compilation

Dans cette version, il n'y a pas d'avancée majeure en ce qui concerne la réduction de la latence des compilateurs, mais des améliorations modestes ont été apportées grâce au travail sur la structure de données des tables de méthodes. Les méthodes ambiguës ont également été identifiées lors de l'insertion, dans l'espoir d'éviter de répéter le travail pour chaque future requête. Malheureusement, le tri d'un ordre partiel nécessite un temps quadratique, et ce temps se manifeste de manière proéminente pendant le chargement du paquet (lorsque les méthodes d'un paquet doivent être insérées dans les tables de méthodes actives).

Une amélioration a été apportée en déplaçant le tri et la détection des ambiguïtés dans l'algorithme de recherche des méthodes correspondantes. Cet algorithme s'exécutant très souvent, les concepteurs ont préalablement pensé qu’il n’était pas utile. L'essentiel étant que la grande majorité des requêtes portent sur des types suffisamment spécifiques pour que la plupart des correspondances possibles puissent être éliminées facilement, ce qui réduit considérablement le nombre d'entrées pour les étapes les plus coûteuses.

La principale amélioration visible ici concerne le chargement des paquets, ce qui ajoute un peu de vitesse supplémentaire en plus des gains obtenus en traitant les invalidations. Des efforts considérables ont été consentis pour affiner les caractéristiques de qualité d’inférence, à la fois en arrêtant rapidement l'analyse lorsqu'elle n'est pas perçue comme n’étant pas utile et en extrayant des informations plus précises lorsque cela est possible. Ces deux aspects peuvent avoir des avantages significatifs pour les codes complexes, tels que les bibliothèques de traçage, qui se ramifient sur un grand nombre d'options de configuration différentes.

Accélération du chargement

Bien que la stratégie de Julia ait toujours été de donner la priorité à la fiabilité et à la reproductibilité sur toutes les autres préoccupations, pour les concepteurs, dans le passé, cela a eu un coût. La solution aux problèmes de fiabilité et de reproductibilité était d'isoler plus complètement les binaires installés et de les compiler en utilisant le framework BinaryBuilder.jl. Les bibliothèques construites à partir de BinaryBuilder.jl sont le plus souvent utilisées par le biais de paquets dits JLL qui fournissent une API normalisée que les paquets Julia peuvent utiliser pour accéder aux binaires fournis.

Cette facilité d'utilisation et la fiabilité de l'installation ont entraîné une augmentation considérable des temps de chargement par rapport au vieux temps où les paquets Julia devaient aveuglément charger toutes les bibliothèques qui se trouvaient sur le chemin de recherche des bibliothèques. Pour illustrer le problème, dans Julia 1.4, le chargement de la pile GTK+3 nécessitait 7 secondes alors qu'il prenait environ 500 ms sur la même machine dans les versions précédentes. Après de nombreux mois de travail acharné et de recherches minutieuses, les concepteurs de Julia ont annoncé avec beaucoup de bonheur que la même pile de bibliothèques prend maintenant moins de 200 ms à charger en utilisant Julia v1.6 sur la même machine.

Le travail consenti pour réduire la taille des paquets JLL a abouti à la création d'un nouveau paquet, JLLWrappers.jl. Ce paquet fournit des macros qui génèrent automatiquement les liaisons nécessaires à un paquet JLL et ce en utilisant le nombre minimum de fonctions et de structures de données possible.

En limitant le nombre de sauvegardes et de structures de données, ainsi qu'en centralisant les éléments de code modèle que chaque paquetage JLL utilise, Julia1.6 est en mesure non seulement d'améliorer considérablement les temps de chargement, mais aussi les temps de compilation. En plus, les améliorations apportées aux API des paquets JLL peuvent désormais être réalisées directement dans JLLWappers.jl sans avoir à redéployer des centaines de JLL.

Nom : VersionjuliaB.png
Affichages : 2139
Taille : 31,8 Ko

L'interaction entre les améliorations du compilateur et les avantages offerts par JLLWrappers a été bien enregistrée pendant le processus de développement, et montre une accélération des temps de chargement pour le paquet GTK3_jll original, non JLLWrapperisé, de son pic de 6,73 secondes sur Julia v1.4 à 2,34 secondes sur Julia v1.6, uniquement grâce aux améliorations du compilateur. L'utilisation de l'implémentation allégée de JLLWrappers pour tous les paquets JLLWrappers pertinents permet de réduire encore le temps de chargement à 140 ms. De bout en bout, cela signifie que ce travail a permis d'accélérer d'environ 50 fois le temps de chargement de grands arbres d'artefacts binaires.

Téléchargements et options de mise en réseau

Les fonctionnalités de téléchargement dans Julia étaient uniquement disponibles à l'aide de la fonction Base.download ou indirectement à travers le gestionnaire de paquets. Les opérations de téléchargement proprement dites étaient effectuées par un processus externe, selon la disponibilité de certains utilitaires sur le système : curl, wget, fetchou PowerShell. Le fait que cette fonctionnalité de frankendownload ait fonctionné est une sorte de miracle, qui n'a fonctionné qu'en raison d'une mise au point minutieuse des options de la ligne de commande au fil des ans. Et bien que cela ait fonctionné la plupart du temps, il y avait quelques inconvénients majeurs à cette approche.

  • la lenteur : le lancement d'un nouveau processus pour chaque téléchargement est coûteux ; mais pire encore, ces processus ne peuvent pas partager les connexions TCP ou réutiliser les connexions TLS déjà négociées, de sorte que chaque téléchargement doit suivre la procédure de connexion TCP SYN/ACK, puis également la poignée de main secrète TLS, ce qui prend beaucoup de temps ;
  • l’incohérence : puisque la manière exacte dont les choses sont téléchargées dépend des installations sur le système, le comportement du téléchargement est très incohérent. Les téléchargements qui fonctionnent sur un système peuvent ne pas fonctionner sur un autre. De plus, tous les problèmes soulevés finissaient souvent hors de portée de Julia ;
  • la flexibilité : les conditions de base pour le téléchargement sont simples : URL en entrée, fichier en sortie. Toutefois, les utilisateurs pouvaient avoir besoin des en-têtes personnalisés dans la requête ou simplement, souhaitaient afficher la progression des téléchargements volumineux.

Dans Julia 1.6, tous les téléchargements sont effectués avec libcurl-7.73.0 via la nouvelle bibliothèque standard Downloads.jl. Le téléchargement est fait à l'intérieur du processus et les connexions TCP+TLS sont partagées et réutilisées. Si le serveur prend en charge HTTP/2, plusieurs demandes adressées à ce serveur peuvent même être multiplexées sur les mêmes connexions HTTPS.

Source : Julia

Et vous ?

Êtes-vous un développeur Julia ? Pourquoi avez-vous opté pour ce langage ?

Quels commentaires faites-vous des améliorations apportées à ce langage ?

Quelles sont vos attentes pour ce langage ?

Voir aussi :

Le langage de programmation Julia serait capable de lire les fichiers CSV dix à vingt fois plus vite que Python et R, selon une étude

Les raisons de l'adoption accélérée du langage Julia : un langage polyvalent, mais plus scientifique, supporte l'introspection et la métaprogrammation, selon Lee Phillips

Le langage de programmation Julia gagne de plus en plus en popularité au sein de la communauté scientifique, depuis janvier 2018

Hands-On Design Patterns and Best Practices with Julia: Proven solutions to common problems in software design for Julia 1.x, un livre de Tom Kwong, critique par Thibaut Cuvelier