Julia est un langage de programmation open source et un écosystème pour le calcul scientifique de haute performance. Conçu par des chercheurs du MIT en 2009 et dévoilé pour la première fois au grand public en 2012, il est doté d’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. Voici, ci-dessous, quelques améliorations apportées à Julia dans la version 1.7 :
Générateur de nombres aléatoires par défaut
Depuis sa toute première version, Julia utilisait le populaire algorithme Mersenne Twister comme générateur de nombres aléatoires par défaut. « Nous savions que nous devions réévaluer ce choix à un moment donné, mais cela ne semblait pas particulièrement urgent jusqu'à ce que le programmeur Chet Hega nous fasse remarquer qu'en changeant d'algorithme nous pouvions non seulement obtenir une accélération significative, mais aussi rendre les flux de nombres aléatoires reproductibles dans les programmes multithreads », révèle l’équipe de développement de Julia.
Développé par Makoto Matsumoto et Takuji Nishimura en 1997, l’algorithme Mersenne Twister (MT) est basé sur un twisted generalised shift feedback register (TGSFR), un type particulier de registre à décalage et rétroaction et tient son nom d’un nombre premier de Mersenne. Il existe au moins deux variantes majeures, la plus répandue étant MT 19937, utilisant le nombre premier de Mersenne 219937-1 et présente les propriétés suivantes :
- sa période est de 219937-1 ;
- il est plus rapide que la plupart des autres générateurs ;
- il est uniformément distribué sur un grand nombre de dimensions (623 pour les nombres de 32 bits) ;
- il est aléatoire quel que soit le poids du bit considéré, suivant les tests Diehard, mais échoue systématiquement sur deux des tests BigCrush de TestU01.
Mersenne Twister est célèbre pour avoir une période exceptionnellement longue (219937-1), mais cela nécessite un état de taille correspondante, et n'est pas vraiment nécessaire pour toute application pratique. L’équipe Julia a également utilisé des états de générateur de nombres aléatoires locaux pour la sécurité des threads, ce qui apporte un peu de surcharge et rend les flux aléatoires dépendants de la planification des tâches.
La proposition de Chet a profité de l'état beaucoup plus petit de la famille Xoshiro256 des de générateurs de nombres aléatoires pour mettre un état dans chaque tâche, et le forker à chaque création de tâche. Ainsi, les nombres aléatoires ne dépendent que de la structure de création des tâches d'un programme, et non du programme d'exécution parallèle. « Il a fallu un peu de débat pour que nous soyons tous à l'aise avec l'idée de dépenser de précieux octets d'objets Task de cette manière, mais nous sommes de grands fans de la reproductibilité et la proposition a donc été finalement adoptée », souligne L’équipe de développement de Julia.
Gestionnaire de paquets
Avec la version 1.7 de Julia, si un paquet existe dans un registre mais n'est pas installé, une installation automatique est maintenant proposée lorsqu'un chargement de paquet est tenté dans le shell interactif de niveau supérieur ou de langage. Voici, cidessous, quelques paquets dans l'écosystème construire sur cette fondation pour fournir des abstractions thread-safe :
- tkf/ThreadsX.jl : Des versions parallélisées de certaines fonctions de Base ;
- JuliaConcurrent/ConcurrentCollections.jl : files d'attente et dictionnaires pour l'état partagé ;
- JuliaActors/Actors.jl : des modèles pour rendre la concurrence facile à comprendre et à raisonner ;
- JuliaFolds/FLoops.jl : génère une itération séquentielle et parallèle générique rapide sur des collections complexes ;
- JuliaFolds/Transducers.jl : parallélismes basés sur les threads (foldxt) et les processus (foldxd) avec la même API composable ; i.e. les transducteurs.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | julia> using Foo ERROR: ArgumentError: Package Foo not found in current path: - Run `import Pkg; Pkg.add("Foo")` to install the Foo package. Stacktrace: [1] require(into::Module, mod::Symbol) @ Base ./loading.jl:871 (@1.6) pkg> add Foo ... julia> using Foo julia> Foo Foo |
peut désormais être réalisé avec seulement
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | julia> using Foo │ Package Foo not found, but a package named Foo is available from a registry. │ Install package? │ (@v1.7) pkg> add Foo └ (y/n) [y]: y ... julia> Foo Foo |
Par défaut, le paquet sera installé dans l'environnement actif actuel, sélectionné par y ou par une simple pression sur la touche retour.
Nouveau format de manifeste
Chaque fois qu'un utilisateur ajoute un paquetage dans Julia, le gestionnaire de paquetage (Pkg) écrit un fichier TOML appelé manifest avec la version exacte de toutes les dépendances de ce paquetage. Différentes versions de paquetage peuvent être compatibles avec différentes versions de Julia et la sortie du resolver(l'algorithme qui calcule un ensemble de versions compatibles pour tous les paquetages et dépendances dans le projet) dépend donc de la version de Julia. Il n'est donc pas recommandé d'utiliser un manifeste créé dans une version de Julia avec une autre version de Julia. Il serait pratique que Pkg puisse vous avertir lorsque cela se produit.
Afin d'émettre un tel avertissement, Pkg aurait besoin de savoir quelle version de Julia a généré un manifeste donné. Cependant, le format actuel (ou schéma) du manifeste rend difficile l'ajout d'une telle information. La raison en est que le format est tel que les clés de premier niveau dans le fichier TOML sont les noms des paquets des dépendances. Cela signifie qu'il n'y a pas de place pour ajouter quelque chose comme une entrée julia_version. Bien sûr, il serait possible de créer une entrée spéciale avec ce nom (en supposant que personne ne nommera un paquet exactement julia_version) mais il serait beaucoup plus agréable de ne pas avoir la même entrée "structurelle" faisant référence à deux choses complètement différentes.
Dans la version 1.7, le format du manifeste a été modifié afin que toutes les dépendances soient placées sous une clé [deps] commune. Cela libère l'espace de nom global pour permettre l'ajout d'une entrée julia_version. Cela ouvre également la possibilité d'ajouter de futures données utiles au manifeste. Pkg conservera également le format d'un manifeste existant, de sorte que seuls les nouveaux manifestes auront le nouveau format de manifeste à l'avenir.
Amélioration des performances pour la gestion des registres sur Windows et les systèmes de fichiers distribués
Suite à quelques plaintes concernant la vitesse du gestionnaire de paquets Julia (Pkg) sous Windows et sur les systèmes de fichiers réseau (NFS), l’équipe de développement à améliorer la situation dans Julia 1.7. Ce qui est commun entre ces systèmes est que ce sont des systèmes où les opérations sur les fichiers ont tendance à être significativement plus coûteuses.
Le registre général est le registre par défaut que Pkg utilise pour rechercher des informations sur les paquets. Il est structuré de telle sorte que chaque paquet a quatre fichiers TOML différents. Au moment de la rédaction de cet article, le registre général contient 5761 paquets, ce qui signifie qu'il contient environ 23 000 fichiers. Il y a deux façons pour Pkg d'obtenir des mises à jour pour un registre, soit via le protocole git, soit via HTTPS en utilisant Pkg Server, qui est un moyen communautaire d'héberger des paquets et des registres où le registre est téléchargé sous la forme d'une archive compressée.
Selon certains rapports, sous Windows, le téléchargement initial du registre général prendrait plusieurs minutes, alors que sous Linux et macOS, il ne prend que quelques secondes. La cause principale de ce ralentissement a été diagnostiquée comme étant Windows Defender, qui provoque des ralentissements lors de la fermeture des fichiers, ce qui est très difficile lors de l'extraction de 23 000 petits fichiers. Selon l’équipe Julia, la communauté Rust serait confrontée à un problème similaire lorsqu'il s'agit de décompresser sa documentation.
Étant donné que Julia est fournie avec p7zip et avec la bibliothèque standard Tar.jl, au lieu d'utiliser un pool de threads pour accélérer la fermeture des fichiers, l’équipe a décidé de prendre une autre voie. Il est possible de lire directement le fichier compressé en mémoire sans matérialiser aucun fichier. En faisant cela, le problème de la matérialisation des fichiers est oublié ce qui améliore significativement les performances du registre sous Windows, NFS et autres systèmes de fichiers distribués comme ceux typiquement utilisés dans les systèmes HPC.
À titre d'exemple, voici, l'effet sur un système propre lors de l'installation du paquetage Example à partir de zéro. D'abord, avec les anciennes méthodes de décompression de tous les fichiers (~30 secondes) :
Code : | Sélectionner tout |
1 2 3 4 5 | julia> @time Pkg.add("Example") Installing known registries into `C:\Users\Kristoffer Carlsson\.julia` Resolving package versions... Installed Example ─ v0.5.3 29.509835 seconds (4.81 M allocations: 320.162 MiB, 0.81% gc time) |
Et ensuite avec la nouvelle méthode qui consiste à lire le registre compressé directement en mémoire (~2 secondes) :
Code : | Sélectionner tout |
1 2 3 4 5 | julia> @time Pkg.add("Example") Installing known registries into `C:\Users\Kristoffer Carlsson\.julia` Resolving package versions... Installed Example ─ v0.5.3 1.953665 seconds (2.35 M allocations: 164.310 MiB, 1.96% gc time) |
Meilleure impression du chemin pour les bibliothèques standard dans les erreurs
Le chemin d'accès à une méthode Julia est défini lors de la définition de la méthode. Cela signifie que lorsque l'on utilise une installation de Julia qui a été compilée ailleurs (par exemple les installations officielles de Julia) les chemins pour les méthodes livrées avec Julia se référeront à un serveur de cloud qui a fait la compilation. Comme exemple, ci-dessous le chemin vers le buildworker qui a compilé Julia est indiqué :
Code : | Sélectionner tout |
1 2 3 4 5 | julia> using Random; Random.seed!("seed") ERROR: MethodError: no method matching seed!(::String) Closest candidates are: seed!() at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/Random/src/RNGs.jl:362 ... |
Certains éditeurs permettent d'ouvrir des fichiers en cliquant sur les chemins dans le terminal, mais cela ne fonctionne pas dans des cas comme celui-ci. Dans la version 1.7, cela a été corrigé afin d'imprimer les chemins qui sont valides localement :
Code : | Sélectionner tout |
1 2 3 4 5 | julia> using Random; Random.seed!("seed") ERROR: MethodError: no method matching seed!(::String) Closest candidates are: seed!() at ~/Downloads/julia/share/julia/stdlib/v1.7/Random/src/RNGs.jl:387 ... |
libblastrampoline + MKL.jl
Julia v1.7 introduit une nouvelle bibliothèque de démuxage BLAS appelée libblastrampoline (LBT), qui fournit un moyen flexible et efficace de changer de bibliothèque BLAS au moment de l'exécution. Comme l'API BLAS/LAPACK est "pure" (c'est-à-dire que chaque invocation BLAS/LAPACK est séparée des autres ; il n'y a pas de transfert d'état d'un appel d'API à l'autre), il est possible de changer de backend BLAS pour répondre à un appel d'API client particulier, comme un appel DGEMM pour une multiplication Matrice-Matrice Float64.
Cette absence d'état permet de passer facilement d'un backend BLAS à un autre sans avoir à modifier le code client, et en combinant cela avec une implémentation flexible du wrapper, il est possible de fournir une API unique et cohérente qui s'adapte automatiquement à une variété de fournisseurs BLAS/LAPACK sur toutes les plateformes que Julia supporte.
Le wrapper lui-même consiste en des routines d'assemblage pour sauter à un pointeur de fonction stockée, en utilisant les mêmes morceaux d'assemblage que la Table de Liaison de Procédure (PLT) utilise dans chaque bibliothèque dynamique du système d'exploitation. Ces petites routines d'assemblage efficaces agissent comme un "trampoline", faisant rebondir un appel vers sa véritable destination dans OpenBLAS, MKL, etc...
L'histoire ne s'arrête cependant pas à l'écriture de routines de transfert performantes ; l’équipe de developement de Julia doit également faire face à la complexité des différentes ABI BLAS/LAPACK. La différence d'ABI la plus visible pour l'utilisateur est celle des bibliothèques BLAS qui sont construites pour utiliser des indices 64 bits (ILP64) plutôt que des indices 32 bits (LP64). Mélanger les bibliothèques clientes qui passent un index à un backend BLAS qui attend des index d'une largeur de bit différente peut avoir des conséquences désastreuses, depuis le calcul silencieux d'un résultat erroné jusqu'à la défaillance pure et simple.
Le projet Julia est depuis longtemps partisan de nommer ces deux ABI séparément, en suffixant les symboles BLAS ILP64 pour les différencier du reste du monde et ainsi éviter une confusion fatale, en renommant par exemple dgemm_ en dgemm_64_ notons que le soulignement de fin est une convention gfortran que Julia suit pour maintenir la compatibilité ABI.
Pour gérer cela, LBT exporte deux jeux de symboles ; un jeu LP64 avec les noms que la plupart des logiciels attendent, et un jeu ILP64 avec les noms que beaucoup de logiciels dans le monde de Julia attendent déjà (par exemple suffixés avec 64_). En interne, LBT maintient des tables de transfert pour les fonctions exportées LP64 et ILP64 séparément, permettant un grand degré de flexibilité dans la gestion des backends BLAS/LAPACK.
Une autre différence ABI possible est la dénomination des symboles eux-mêmes (dgemm, dgemm_, dgemm__, _dgemm_ et myblas_dgemm sont tous des noms de symboles BLAS qui ont été vus dans la nature) et donc LBT effectue une recherche simple sur les différentes dénominations possibles lors du chargement d'un backend BLAS/LAPACK.
Le backend Accelerate d'Apple utilise une ABI légèrement différente de l'ABI par défaut de gfortran en ce qui concerne le passage d'arguments de caractères (comme le paramètre 'U' de certaines routines LAPACK marquant quelque chose comme une matrice triangulaire supérieure), et LBT convertit automatiquement vers/depuis cette autre ABI. Enfin, LBT gère certaines API spécifiques au fournisseur, comme le réglage du nombre de threads des bibliothèques dorsales, via un point d'entrée unique.
La plupart des utilisateurs n'auront jamais besoin d'interagir directement avec LBT, cependant, pour ceux qui sont intéressés, il est recommendé de regarder les métadonnées que LBT suit sur les bibliothèques qui sont actuellement chargées :
Code : | Sélectionner tout |
1 2 3 4 | julia> LinearAlgebra.BLAS.lbt_get_config() LinearAlgebra.BLAS.LBTConfig Libraries: └ [ILP64] libopenblas64_.so |
Cela montre qu'une seule bibliothèque ILP64 est actuellement chargée, et cette bibliothèque est libopenblas64_.so. Le chargement d'une bibliothèque LP64 (telle que celle fournie par OpenBLAS32_jll) change quelque peu le résultat :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | julia> LinearAlgebra.BLAS.lbt_forward(OpenBLAS32_jll.libopenblas_path) 4860 julia> LinearAlgebra.BLAS.lbt_get_config() LinearAlgebra.BLAS.LBTConfig Libraries: ├ [ILP64] libopenblas64_.so └ [ LP64] libopenblas.so |
Le chargement de MKL fait basculer instantanément la configuration vers l'utilisation de MKL, et toutes les invocations BLAS à partir de ce moment seront effectuées par les noyaux écrits par Intel à la place. Il est possible de créer des configurations vraiment complexes (par exemple en superposant une bibliothèque qui ne fournit que quelques symboles BLAS à OpenBLAS qui peut fournir le reste) mais pour la plupart des utilisateurs l'impact principal sera simplement qu'il n'est plus nécessaire de recompiler Julia quand vous voulez utiliser une autre bibliothèque BLAS.
Notons que le choix de Julia d'utiliser ILP64 ou LP64 est toujours une décision de compilation, et que même si l’utilisateur charge OpenBLAS32_jll pour fournir des symboles LP64, Julia utilisera toujours ILP64 sur les plateformes 64-bit par défaut. La raison principale pour supporter LP64 sur les plateformes 64 bits est de fournir les symboles pour d'autres programmes qui peuvent être liés dans l'espace de noms de Julia, comme l'utilisation de PyCall pour charger numpy.
Amélioration des inférences
Cette version vient avec de nombreuses améliorations de l'inférence de type. Avec ces améliorations, Julia 1.7 déduira plus "intelligemment" les types du programme et améliorera les performances. Plus particulièrement, 1.7 peut propager les contraintes de type qui peuvent être dérivées des conditions isa et === de manière inter-procédurale (c'est-à-dire à travers tous les appels de fonction). Certains programmes Julia sont écrits de manière à ce que leur comportement change en fonction des types d'exécution, et de tels programmes peuvent s'exécuter beaucoup plus rapidement grâce au gain d'inferrabilité de cette amélioration. Par exemple, maintenant il n'y a pas de différence d'inférrabilité entre x === nothing et isnothing(x) (et donc vous n'avez plus besoin de vous rappeler cette astuce de performance) :
Code : | Sélectionner tout |
1 2 3 | julia> code_typed((Union{Nothing,Int},); optimize=false) do x return isnothing(x) ? 0 : x end |> first |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | --- v1.6 +++ v1.7 @@ -1,6 +1,6 @@ CodeInfo( 1 ─ %1 = Main.isnothing(x)::Bool └── goto #3 if not %1 2 ─ return 0 -3 ─ return x -) => Union{Nothing, Int64} +3 ─ return x::Int64 +) => Int64 |
Bien entendu, cette propagation de contraintes interprocédurales fonctionne pour des fonctions génériques arbitraires :
Code : | Sélectionner tout |
1 2 3 4 | julia> ispositive(a) = isa(a, Number) && a > 0; julia> code_typed((Union{Nothing,Int},); optimize=false) do x return ispositive(x) ? x : 0 end |> first |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | --- v1.6 +++ v1.7 @@ -1,6 +1,6 @@ CodeInfo( 1 ─ %1 = Main.ispositive(x)::Bool └── goto #3 if not %1 -2 ─ return x +2 ─ return x::Int64 3 ─ return 0 -) => Union{Nothing, Int64} +) => Int64 |
Une autre amélioration remarquable est la propagation plus rapide des constantes. Julia 1.7 peut remplacer plus de calculs d'exécution par des constantes pré-calculées, et éliminer le code mort en résolvant les branches conditionnelles au moment de la compilation. Par exemple, dans 1.7, les calculs de fonctions spéciales peuvent être entièrement repliés au moment de la compilation :
Code : | Sélectionner tout |
1 2 3 | julia> code_typed((Int,)) do n n + sin(sum(sincos(42))) # no runtime computation of `sum(sincos(42))` in 1.7! end |> first |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | --- v1.6 +++ v1.7 @@ -1,32 +1,5 @@ CodeInfo( -1 ─ %1 = Base.muladd_float(0.16933292771007588, 2.7557313707070068e-6, -0.0001984126982985795)::Float64 -│ %2 = Base.muladd_float(0.16933292771007588, %1, 0.00833333333332249)::Float64 -│ %3 = Base.muladd_float(0.16933292771007588, 1.58969099521155e-10, -2.5050760253406863e-8)::Float64 -│ ... many runtime computations ... -│ %27 = invoke Main.sin(%26::Float64)::Float64 -│ %28 = Base.sitofp(Float64, n)::Float64 -│ %29 = Base.add_float(%28, %27)::Float64 -└── return %29 +1 ─ %1 = Base.sitofp(Float64, n)::Float64 +│ %2 = Base.add_float(%1, -0.9678422808766897)::Float64 +└── return %2 ) => Float64 |
Les littéraux des tableaux multidimensionnels
Les tableaux multidimensionnels, notamment ceux à trois dimensions ou plus, sont des constructions utiles pour la programmation scientifique et l'apprentissage automatique. Cependant, les langages de programmation n'ont pas rendu facile la création et le travail avec eux. Julia a eu des méthodes de première classe pour travailler avec des tableaux multidimensionnels. Cependant, jusqu'à la version 1.6, il n'y avait aucun moyen de les créer avec une syntaxe pure et un coût d'allocation minimal. Vous deviez d'abord allouer des tableaux à 1 ou 2 dimensions et ensuite reshape(), ou cat() les rassembler une dimension à la fois. Il était également difficile de créer une matrice à une colonne et des tableaux à un seul élément de dimension supérieure.
Avec Julia v1.7, nous avons ajouté une syntaxe pour vous permettre d'écrire un littéral pour les tableaux multidimensionnels. Cette nouvelle syntaxe rend les tableaux multidimensionnels beaucoup plus faciles à manipuler dans Julia qu'ils ne l'étaient auparavant, et nous pensons qu'elle se compare favorablement avec la création de tableaux multidimensionnels dans d'autres langages :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Julia v1.7: [1 2 ; 3 4 ;;; 5 6 ; 7 8] or [1 ; 3 ;; 2 ; 4 ;;; 5 ; 7 ;; 6 ; 8] Python with Numpy: import numpy as np np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) MATLAB: A = [1 2; 3 4] A(:,:,2) = [5 6; 7 8] R: array(c(1, 3, 2, 4, 5, 7, 6, 8), dim = c(2, 2, 2)) |
La syntaxe est une simple extension de la syntaxe actuelle : un point-virgule supplémentaire == une dimension supplémentaire :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | julia> [1 2 ; 3 4] 2×2 Matrix{Int64}: 1 2 3 4 julia> [1 2 ;;; 3 4] 1×2×2 Array{Int64, 3}: [:, :, 1] = 1 2 [:, :, 2] = 3 4 julia> [1 2 ;;;; 3 4] 1×2×1×2 Array{Int64, 4}: [:, :, 1, 1] = 1 2 [:, :, 1, 2] = 3 4 julia> using BenchmarkTools julia> @btime [1 2 ;;;; 3 4]; 44.838 ns (2 allocations: 160 bytes) julia (v1.6)> @btime cat([1 2], [3 4], dims = 4); # clear, but slow, and gets worse with more dimensions 1.380 μs (23 allocations: 1.05 KiB) julia (v1.6)> @btime reshape([1; 2; 3; 4], (1, 2, 1, 2)); # fast, but intent less clear 65.884 ns (2 allocations: 192 bytes) |
Il s'agit d'une amélioration substantielle des performances pour cette opération de base, et le différentiel s'améliore grandement avec l'augmentation des dimensions. Pour faciliter la lecture d'une expression de tableau plus grande, les sauts de ligne sont bien sûr tolérés :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | julia> [ 1 2 3 4 ;;; 5 6 7 8 ] 2×2×2 Array{Int64, 3}: [:, :, 1] = 1 2 3 4 [:, :, 2] = 5 6 7 8 |
Cette syntaxe permet également d'écrire des tableaux dans l'ordre des colonnes plutôt que dans celui des lignes, en utilisant des ; ; à la place des espaces pour les lignes :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | julia> [1 ; 2 ;; 3 ; 4 ;;; 5 ; 6 ;; 7 ; 8] 2×2×2 Array{Int64, 3}: [:, :, 1] = 1 3 2 4 [:, :, 2] = 5 7 6 8 |
Les points-virgules inférieurs sont prioritaires par rapport aux points-virgules supérieurs, de sorte que l'expression ci-dessus est équivalente à :
Code : | Sélectionner tout |
julia> [[[1 ; 2] ;; [3 ; 4]] ;;; [[5 ; 6] ;; [7 ; 8]]];
Support pour Apple Silicon
Julia 1.7 est aussi la première version qui fonctionne sur Apple Silicon, par exemple la famille M1 de CPUs ARM. La planification de cette fonctionnalité a commencé il y a plus d'un an, peu après l'annonce par Apple de ses nouvelles puces. Les processeurs Apple Silicon sont des processeurs SoC et SiP utilisant l'architecture ARM conçus par Apple. Ils sont la base des appareils iPhone, iPad et Apple Watch ainsi que de produits tels que le HomePod, l'iPod touch et l'Apple TV.
La route vers un support pour Apple Silicon a été initialement compliquée par l'absence d'un compilateur Fortran pour la nouvelle plateforme, qui est nécessaire pour construire l'une des dépendances binaires de Julia, à savoir OpenBLAS. Bien sûr, Julia n'était pas le seul projet open source pour le calcul numérique affecté par ce problème, qui a finalement été résolu par la disponibilité d'un fork de GCC.
Bien que l’équipe de développement de Julia soit maintenant en mesure de fournir des binaires Julia préconstruits pour cette plateforme, son support est actuellement considéré comme de niveau 3, ce qui signifie qu'il est expérimental et que des bugs spécifiques sont à prévoir. L'enquête Julia User & Developer Survey 2021 a montré que 5 % des utilisateurs de Julia utilisaient déjà Julia sur cette plateforme avant qu'une version stable officielle ne soit publiée pour elle.
L’équipe de developpement de Julia travail déjà sur la version 1.8 du langage de programmation.
Source : Julia
Et vous ?
Entre Julia, Python et R, quel langage utilisez-vous pour la science des données ?
Que pensez-vous de Julia ?
Quelle amélioration de la version 1.7 vous interesse le plus ?
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
La version 1.6 du langage Julia est disponible, elle apporte une réduction de la latence des compilateurs et supprime les recompilations inutiles
L'équipe de développement de Julia a publié la première version admissible de Julia 1.7, mais elle n'est pas prête pour la production comme c'était le cas avec les précédentes RC
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