Julia est un langage de programmation dynamique de haut niveau et à usage général, toujours conçu pour être rapide et productif. Julia est notamment utilisé pour la science des données, l'intelligence artificielle, l'apprentissage automatique, la modélisation et la simulation, le plus souvent pour l'analyse numérique et la science informatique.
Après deux alphas, deux bêtas et quatre release candidates, la version 1.11 de Julia est enfin disponible le 8 octobre 2024. Avec la sortie de la version 1.11, la version 1.10 devient la version LTS, qui était auparavant la version 1.6. Voici un aperçu de certains des points forts de la version 1.11.
Array maintenant implémenté dans Julia, nouveau type Memory
Avant Julia 1.11, Array était un objet spécial dans Julia. Les opérations telles que le redimensionnement et la création devaient être faites entièrement en C, ce qui créait une surcharge et rendait une partie du code beaucoup plus difficile à écrire et difficile à optimiser pour le compilateur. Array avaient également des fonctionnalités qui n'étaient pas nécessaires pour certaines utilisations (par exemple le redimensionnement et les dimensions multiples), ce qui imposait un petit coût.
Pour remédier à cela, cette version ajoue un nouveau type Memory de niveau inférieur, qui permet de réimplémenter tous les Array dans le code Julia. Cela a permis de déplacer une grande partie de la complexité autour du redimensionnement et de la copie de Array dans du code Julia pur. Et cela a permis à quelques types de données importants, qui n'ont pas besoin de toutes les fonctionnalités de Array (comme Dict), d'éviter une petite quantité de surcharge. Cela a conduit à de grandes améliorations de performance. Par exemple, push ! sur Array est maintenant ~2x plus rapide, et plusieurs types dans Base utilisent maintenant un peu moins de mémoire.
Nouveau mot-clé public
Dans les versions précédentes de Julia, il n'y avait pas de "moyen programmatique" de savoir si un nom non exporté était considéré comme faisant partie de l'API publique ou non. Au lieu de cela, la ligne directrice était essentiellement que si ce n'était pas dans le manuel, alors ce n'était pas public, ce qui était un peu décevant.
Pour remédier à cela, il existe désormais un mot-clé public dans Julia qui peut être utilisé pour indiquer qu'un nom non exporté fait partie de l'API publique. Il est maintenant possible de vérifier si un nom est public ou non avec la méthode (publique) Base.ispublic(m::Module, name::Symbol), et ceci est par exemple utilisé par le système d'aide dans la REPL pour indiquer si un nom documenté n'est pas public :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | help?> GC.in_finalizer │ Warning │ │ The following bindings may be internal; they may change or be removed in future versions: │ │ • Base.GC.in_finalizer GC.in_finalizer()::Bool Returns true if the current task is running a finalizer, returns false otherwise. Will also return false within a finalizer which was inlined by the compiler's eager finalization optimization, or if finalize is called on the finalizer directly. |
Version du manifeste
Les fichiers Manifest.toml peuvent maintenant être renommés dans le format Manifest-v{major}.{minor}.toml pour être préférentiellement repris par la version donnée de Julia. Par exemple, dans le même dossier, un Manifest-v1.11.toml sera utilisé par la v1.11 et Manifest.toml par toutes les autres versions de Julia. Cela facilite la gestion des environnements pour plusieurs versions de Julia en même temps. Pour créer un tel manifeste, il est recommandé de renommer un manifeste déjà généré, plutôt que de commencer avec un fichier vide.
Amélioration de la complétion de tabulation et des indices dans le REPL
La complétion de tabulation est devenue plus puissante dans la version 1.11 et a gagné des indications en ligne quand il y a une complétion singulière disponible qui peut être complétée avec tabulation.
Si vous préférez ne pas activer le hinting, désactivez-le via votre startup.jl avec
Code : | Sélectionner tout |
1 2 3 4 5 | atreplinit() do repl if VERSION >= v"1.11.0-0" repl.options.hint_tab_completes = false end end |
Section Sources dans Project.toml dans Pkg.jl
Auparavant, pour pouvoir instancier un environnement qui utilisait des dépendances non enregistrées, il fallait que le fichier manifeste soit disponible, car ce fichier donnait des informations sur, par exemple, l'URL à laquelle les dépendances non enregistrées étaient disponibles. Désormais, ces informations peuvent être spécifiées dans le fichier Project, par exemple :
Code : | Sélectionner tout |
1 2 | [sources] MyUnregisteredPackage = {url = "https://github.com/JuliaLang/MyUnregisteredPackage.jl"} |
Délocalisation des fichiers de précompilation
L'activation de la relocalisation des fichiers cache, ainsi que d'autres améliorations apportées par la v1.11, aideront Pkg.jl à servir les fichiers cache à l'avenir.
Pour activer automatiquement la relocalisabilité, suivez les meilleures pratiques de Pkg.jl, c'est-à-dire ne supposez pas que le code de votre paquetage finisse dans un emplacement stable ou accessible en écriture. Au lieu de cela, utilisez des outils existants comme Artifacts.jl, Scratch.jl ou Preferences.jl pour rendre votre paquetage autonome, immuable et relocalisable.
L'utilisation de paquets non relocalisables continue de fonctionner comme auparavant. Toute tentative de relocalisation d'un tel paquet ne devrait entraîner qu'une surcharge de re-précompilation.
Pièges de la relocalisation :
L'utilisation de @__DIR__, @__FILE__ "brûle" les chemins absolus dans les images des paquets.
En général, évitez les chemins absolus ou relatifs en dehors du répertoire racine du paquet.
Excision de la stdlib
Après l'introduction des images de paquets pour la mise en cache native dans Julia 1.10, l'équipe de Julia a commencé le processus de déplacement des bibliothèques standard hors de l'image-système. Cela permet de réduire la taille de l'image-système de Julia et d'accélérer le démarrage des petits scripts.
Code : | Sélectionner tout |
1 2 3 4 | % hyperfine 'julia +1.10 --startup-file=no -e "1+1"' Benchmark 1: julia +1.10 --startup-file=no -e "1+1" Time (mean ± σ): 113.2 ms ± 2.8 ms [User: 108.9 ms, System: 25.9 ms] Range (min … max): 109.2 ms … 117.9 ms 25 runs |
Code : | Sélectionner tout |
1 2 3 4 | % hyperfine 'julia +1.11 --startup-file=no -e "1+1"' Benchmark 1: julia +1.11 --startup-file=no -e "1+1" Time (mean ± σ): 91.9 ms ± 3.0 ms [User: 72.5 ms, System: 19.1 ms] Range (min … max): 88.7 ms … 100.8 ms 29 runs |
Les valeurs cadrées (ScopedValues)
ScopedValues est un nouveau type de données supporté par le runtime qui fournit une alternative aux globales pour les paramètres de configuration.
L'exemple ci-dessous utilise les ScopedValues pour mettre en œuvre le contrôle des autorisations dans une application web. Lors du traitement initial d'une demande, le niveau de permission ne doit être vérifié qu'une seule fois et tous les traitements ultérieurs de la demande hériteront dynamiquement de l'état de la ScopedValue. Dynamiquement signifie que l'état des ScopedValues est propagé aux fonctions et tâches enfants.
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 | using Base.ScopedValues const LEVEL = ScopedValue(:GUEST) function serve(request, response) level = isAdmin(request) ? :ADMIN : :GUEST with(LEVEL => level) do Threads.@spawn handle(request, response) end end function open(connection::Database) level = LEVEL[] if level !== :ADMIN error("Access disallowed") end # ... open connection end function handle(request, response) # ... open(Database(#=...=#)) # ... end |
Nouveau point d'entrée principal
Le point d'entrée de Julia a été standardisé à Main.main(args). Ceci doit être explicitement choisi en utilisant la macro @main. Lorsque l'option est choisie et que julia est invoquée pour exécuter un script ou une expression (c'est-à-dire en utilisant julia script.jl ou julia -e expr), julia exécutera ensuite automatiquement la fonction Main.main. Ceci a pour but d'unifier les flux de script et de compilation, où le chargement du code peut avoir lieu dans le compilateur et l'exécution de Main.main peut avoir lieu dans l'exécutable résultant. Pour une utilisation interactive, il n'y a pas de différence sémantique entre la définition d'une fonction main et l'exécution du code directement à la fin du script.
La macro @time signale désormais les conflits de verrouillage
La macro @time signale désormais toute contention de verrou au sein de l'appel en cours de chronométrage, sous la forme d'un nombre de conflits de verrou. Un conflit de verrouillage se produit lorsqu'une tâche tente de verrouiller un ReentrantLock déjà verrouillé, et peut indiquer des problèmes de conception susceptibles d'entraver les performances simultanées.
Dans cet exemple, le nombre de conflits est clairement attendu, mais les verrous peuvent être enfouis profondément dans le code de la bibliothèque, ce qui les rend difficiles à détecter autrement.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | julia> Threads.nthreads() 6 julia> function foo() l = ReentrantLock() Threads.@threads for i in 1:Threads.nthreads() lock(l) do sleep(1) end end end foo (generic function with 1 method) julia> @time foo() 6.069761 seconds (28.14 k allocations: 1.410 MiB, 5 lock conflicts, 1.34% compilation time) |
Amélioration de l'inférence
Dans la version 1.11, plusieurs nouvelles fonctionnalités ont été ajoutées à l'inférence.
- Inférence de type d'exception
La première fonctionnalité est l'inférence des types d'exception. Le compilateur Julia est maintenant capable de déduire les types des objets d'exception, améliorant de manière significative la stabilité des types dans les blocs catch. Par exemple, dans la méthode demo_exc_inf suivante, vous pouvez voir que le type de l'objet err est inféré comme ::Union{Float64, DomainError} au lieu de ::Any (comme c'était le cas dans la v1.10) :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
32
33
34
35
36
37
38
39
40julia> function demo_exc_inf(x::Float64) local v try v = x > 0 ? sin(x) : throw(x) catch err # the type of `err` can be inferred >v1.11 if err isa DomainError v = zero(x) else v = err end end return v end demo_exc_inf (generic function with 1 method) julia> only(code_typed(demo_exc_inf, (Float64,); optimize=false)) CodeInfo( 1 ── Core.NewvarNode(:(v))::Any 2 ── %2 = enter #8 3 ── %3 = (x > 0)::Bool └─── goto #5 if not %3 4 ── (@_5 = Main.sin(x))::Float64 └─── goto #6 5 ── (@_5 = Main.throw(x))::Union{} 6 ┄─ %8 = @_5::Float64 │ (v = %8)::Float64 └─── $(Expr(:leave, :(%2))) 7 ── goto #12 8 ┄─ (err = $(Expr(:the_exception)))::Union{Float64, DomainError} │ %13 = err::Union{Float64, DomainError} │ %14 = (%13 isa Main.DomainError)::Bool └─── goto #10 if not %14 9 ── (v = Main.zero(x))::Core.Const(0.0) └─── goto #11 10 ─ %18 = err::Float64 └─── (v = %18)::Float64 11 ┄ $(Expr(:pop_exception, :(%2)))::Core.Const(nothing) 12 ┄ %21 = v::Float64 └─── return %21 ) => Float64
Notez cependant que la précision de l'inférence de type d'exception n'est pas encore très élevée. Elle ne fonctionne généralement bien que lorsque le bloc try contient des fonctions de base. En particulier, elle échoue souvent à fournir des résultats précis pour les blocs try qui peuvent appeler des fonctions impliquant des appels externes, telles que ccall. - Analyse des échappements
La prochaine fonctionnalité est l'analyse des échappements au niveau de Julia (Core.Compiler.EscapeAnalysis). Bien que Core.Compiler.EscapeAnalysis ait été implémenté dans la version 1.8, il n'a pas été activé dans le pipeline de compilation actuel en raison de problèmes liés à sa précision et à sa latence.
En tant que premier pas vers l'utilisation de EscapeAnalysis pour diverses optimisations, dans la version 1.11, elle est maintenant activée de manière sélective dans le pipeline de compilation actuel dans le but d'améliorer la précision de l'analyse des effets pour les méthodes impliquant des allocations mutables non escamotables.
Actuellement, la précision de EscapeAnalysis n'est pas très élevée, et il est difficile d'obtenir de bons résultats pour les fonctions comportant des opérations mémoire complexes. Par conséquent, il n'a pas encore été utilisé à d'autres fins.
Cependant, l'équipe de Julia prévoit de continuer à l'améliorer et de l'appliquer à diverses optimisations telles qu'une propagation plus agressive des constantes, de meilleurs SROA (remplacements scalaires d'agrégats), l'optimisation de finalizer et l'allocation de la pile.
Source : "Julia 1.11 Highlights"
Et vous ?
Que pensez-vous de cette mise à jour ?
Voir aussi :
La version 1.10 de Julia est disponible, avec de nouvelles fonctionnalités, dont des améliorations de performance et des changements de comportement marginaux et non perturbateurs
Le potentiel du langage de programmation Julia pour le calcul en physique des hautes énergies, un domaine qui nécessite d'énormes quantités de calcul et de stockage, selon une étude scientifique
Adoption du langage de programmation Julia : Logan Kilpatrick, défenseur de la communauté des développeurs Julia, livre son analyse, dans un billet de blog