Introduction
Depuis la sortie de .NET 6 en novembre 2021, l'écosystème .NET a évolué très rapidement. En l'espace de trois ans, nous avons vu arriver .NET 7, .NET 8 et maintenant, en novembre 2024, .NET 9. Cette succession rapide de versions soulève une question importante : est-il vraiment utile de migrer vers la dernière version ? Quel impact ces mises à jour ont-elles sur les performances de nos applications ? Dans cet article, nous examinons les gains de performance observés entre .NET 6, .NET 8 et .NET 9 afin de déterminer si une mise à niveau vaut réellement la peine pour vos projets ou ceux de vos clients.
La question centrale pour nous est toujours la suivante :
Puis-je espérer une accélération sensible de mon application si je passe à la dernière version de .NET, sans devoir modifier mon code ?
Dans cet article, nous comparons les améliorations de performance de .NET 6, .NET 8 et .NET 9 sur la base des benchmarks et tests officiels de Microsoft. Nous cherchons à savoir si les dernières versions de la plateforme .NET apportent une réelle valeur ajoutée.
Environnement
Pour comparer les améliorations de performance entre .NET 6, .NET 8 et .NET 9, nous avons utilisé les benchmarks officiels de Microsoft ainsi que la .NET Benchmarking Library pour nos propres tests.
Tous les benchmarks ont été exécutés sur un système Windows 11 avec un processeur Intel Core i9-13900H de 13e génération. Les versions .NET utilisées pour les tests sont :
- .NET 6.0 : .NET 6.0.33 (6.0.3324.36610), X64 RyuJIT AVX2
- .NET 8.0 : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
- .NET 9.0 : .NET 9.0.0 (9.0.24.43107), X64 RyuJIT AVX2
Qu'avons-nous comparé ?
Il existe de nombreuses améliorations de performance dans .NET 6, .NET 8 et .NET 9, mais beaucoup concernent des cas de niche qui ne sont pas pertinents pour toutes les applications. LINQ, JSON et les reflections sont en revanche des éléments importants des applications modernes, c'est pourquoi nous avons choisi de les comparer.
LINQ
Nous commençons par LINQ, car ses performances ont souvent été critiquées dans le passé. Nous avons testé les opérations LINQ suivantes...
public class LinqBenchmarks
{
private static readonly IEnumerable<int> _range = Enumerable.Range(0, 1000);
private readonly IEnumerable<int> _list = _range.ToList();
private readonly IEnumerable<int> _arrayDistinct = _range.ToArray().Distinct();
private readonly IEnumerable<int> _appendSelect = _range.ToArray().Append(42).Select(i => i * 2);
private readonly IEnumerable<int> _rangeReverse = _range.Reverse();
private readonly IEnumerable<int> _listDefaultIfEmptySelect = _range.ToList().DefaultIfEmpty().Select(i => i * 2);
private readonly IEnumerable<int> _listSkipTake = _range.ToList().Skip(500).Take(100);
private readonly IEnumerable<int> _rangeUnion = _range.Union(Enumerable.Range(500, 1000));
[Benchmark] public bool Any() => _list.Any(i => i == 1000);
[Benchmark] public bool All() => _list.All(i => i >= 0);
[Benchmark] public int Count() => _list.Count(i => i == 0);
[Benchmark] public int First() => _list.First(i => i == 999);
[Benchmark] public int Single() => _list.Single(i => i == 0);
[Benchmark] public int DistinctFirst() => _arrayDistinct.First();
[Benchmark] public int AppendSelectLast() => _appendSelect.Last();
[Benchmark] public int RangeReverseCount() => _rangeReverse.Count();
[Benchmark] public int DefaultIfEmptySelectElementAt() => _listDefaultIfEmptySelect.ElementAt(999);
[Benchmark] public int ListSkipTakeElementAt() => _listSkipTake.ElementAt(99);
[Benchmark] public int RangeUnionFirst() => _rangeUnion.First();
}
...et nous avons résumé ici les résultats les plus intéressants :
Les résultats montrent que les performances des opérations LINQ se sont nettement améliorées dans .NET 9 par rapport à .NET 6 et .NET 8. Si la montée de .NET 6 à .NET 8 était déjà importante, le saut de .NET 8 à .NET 9 est encore plus impressionnant.
JSON
JSON est un autre élément fondamental des applications modernes. Nous avons testé les performances de sérialisation et de désérialisation JSON dans .NET 6, .NET 8 et .NET 9.
public class JsonBenchmarks
{
private static readonly JsonSerializerOptions s_options = new()
{
Converters = { new JsonStringEnumConverter() },
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
};
[Params(BindingFlags.Default, BindingFlags.NonPublic | BindingFlags.Instance)]
public BindingFlags _value;
private byte[]? _jsonValue;
private Utf8JsonWriter _writer = new(Stream.Null);
[GlobalSetup]
public void Setup() => _jsonValue = JsonSerializer.SerializeToUtf8Bytes(_value, s_options);
[Benchmark]
public void Serialize()
{
_writer.Reset();
JsonSerializer.Serialize(_writer, _value, s_options);
}
[Benchmark]
public BindingFlags Deserialize() =>
JsonSerializer.Deserialize<BindingFlags>(_jsonValue, s_options);
}
Les résultats des benchmarks JSON ressemblent à ceci :
Les performances de sérialisation et de désérialisation JSON se sont améliorées dans .NET 9 par rapport à .NET 6 et .NET 8.
Reflections
Même si l'on devrait éviter les reflections dans les applications modernes, il existe toujours des cas où elles restent nécessaires.
public class ReflectionBenchmarks
{
private static object s_staticReferenceField = new object();
private object _instanceReferenceField = new object();
private static int s_staticValueField = 1;
private int _instanceValueField = 2;
private object _obj = new();
private static readonly Type _type = typeof(ReflectionBenchmarks);
private readonly FieldInfo _staticReferenceFieldInfo = _type
.GetField(nameof(s_staticReferenceField), BindingFlags.NonPublic | BindingFlags.Static)!;
private readonly FieldInfo _instanceReferenceFieldInfo = _type
.GetField(nameof(_instanceReferenceField), BindingFlags.NonPublic | BindingFlags.Instance)!;
private readonly FieldInfo _staticValueFieldInfo = _type
.GetField(nameof(s_staticValueField), BindingFlags.NonPublic | BindingFlags.Static)!;
private readonly FieldInfo _instanceValueFieldInfo = _type
.GetField(nameof(_instanceValueField), BindingFlags.NonPublic | BindingFlags.Instance)!;
[Benchmark] public object? GetStaticReferenceField() => _staticReferenceFieldInfo.GetValue(null);
[Benchmark] public void SetStaticReferenceField() => _staticReferenceFieldInfo.SetValue(null, _obj);
[Benchmark] public object? GetInstanceReferenceField() => _instanceReferenceFieldInfo.GetValue(this);
[Benchmark] public void SetInstanceReferenceField() => _instanceReferenceFieldInfo.SetValue(this, _obj);
[Benchmark] public int GetStaticValueField() => (int)_staticValueFieldInfo.GetValue(null)!;
[Benchmark] public void SetStaticValueField() => _staticValueFieldInfo.SetValue(null, 3);
[Benchmark] public int GetInstanceValueField() => (int)_instanceValueFieldInfo.GetValue(this)!;
[Benchmark] public void SetInstanceValueField() => _instanceValueFieldInfo.SetValue(this, 4);
}
Ce qui ressort ici, c'est que .NET 8 peut être plus lent que .NET 6 dans certains cas. .NET 9, en revanche, montre une amélioration nette sur l'ensemble des mesures.
Conclusion
Les améliorations de performance dans .NET 9 sont impressionnantes. Les benchmarks montrent que .NET 9 offre des gains notables dans de nombreux domaines. Si vous cherchez un moyen d'améliorer les performances de vos applications, une mise à niveau vers .NET 9 peut donc être une bonne option, sans nécessairement exiger d'adaptation du code. Il reste toutefois important de noter que les résultats peuvent varier selon l'application. Des tests et benchmarks réalisés sur votre propre contexte restent indispensables.
Besoin d'aide ?
Vous souhaitez migrer vers .NET 9 ou optimiser les performances de vos applications .NET, mais vous ne savez pas par où commencer ? Nous pouvons vous aider. Contactez-nous via notre page de contact et voyons ensemble comment rendre vos applications plus rapides et plus efficaces.
Code source
Le code source complet des benchmarks est disponible sur GitHub.




