DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Uncategorized»Comment les performances de LINQ ont-elles été améliorées dans .NET 7 ?
    Uncategorized

    Comment les performances de LINQ ont-elles été améliorées dans .NET 7 ?

    janvier 24, 2023
    Comment les performances de LINQ ont-elles été améliorées dans .NET 7 ?
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    La nouvelle version de .NET a amélioré les performances des méthodes Min, Max, Average et Sum pour les tableaux et les listes. À votre avis, de combien leur vitesse d’exécution a-t-elle augmenté ? Deux fois ou cinq fois ? Non, ils sont allés encore plus vite. Voyons comment cela a été réalisé.

    Comment LINQ s’est-il amélioré ?

    LINQ (Language-Integrated Query) est un langage de requête simple et pratique. Il vous permet d’exprimer des opérations complexes de manière simple. Presque tous les développeurs .NET utilisent LINQ. Cependant, cette simplicité d’utilisation se fait au prix de la vitesse d’exécution et de l’allocation de mémoire supplémentaire. Dans la plupart des situations, cela n’a pas d’effet significatif. Cependant, dans les cas où les performances sont critiques, ces limitations peuvent être assez désagréables.

    Ainsi, la récente mise à jour a amélioré les performances des méthodes suivantes :

    • Enumérable.Max
    • Enumérable.Min
    • Enumérable.Moyenne
    • Enumerable.Sum

    Voyons comment leurs performances ont augmenté en utilisant le benchmark suivant :

    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Running;
    using System.Collections.Generic;
    using System.Linq;
    
    
    [MemoryDiagnoser(displayGenColumns: false)]
    public partial class Program
    {
      static void Main(string[] args) =>
        BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
    
      [Params (10, 10000)]
      public int Size { get; set; }
      private IEnumerable<int> items;
    
      [GlobalSetup]
      public void Setup()
      {
        items = Enumerable.Range(1, Size).ToArray();
      }  
    
      [Benchmark]
      public int Min() => items.Min();
    
      [Benchmark]
      public int Max() => items.Max();
    
      [Benchmark]
      public double Average() => items.Average();
    
      [Benchmark]
      public int Sum() => items.Sum();
    }

    Résultats de référence :

    Méthode

    Durée

    Taille

    Moyenne

    Rapport

    Alloué

    Min

    .NET 6.0

    dix

    75,491 ns

    1,00

    32 B

    Min

    .NET 7.0

    dix

    7,749 ns

    0,10

    –

    Max

    .NET 6.0

    dix

    71,128 ns

    1,00

    32 B

    Max

    .NET 7.0

    dix

    6,493 ns

    0,09

    –

    Moyenne

    .NET 6.0

    dix

    68,963 ns

    1,00

    32 B

    Moyenne

    .NET 7.0

    dix

    7,315 ns

    0,11

    –

    Somme

    .NET 6.0

    dix

    69,509 ns

    1,00

    32 B

    Somme

    .NET 7.0

    dix

    9,058 ns

    0,13

    –

    Min

    .NET 6.0

    10000

    61 567,392 ns

    1,00

    32 B

    Min

    .NET 7.0

    10000

    2 967,947 ns

    0,05

    –

    Max

    .NET 6.0

    10000

    56 106,592 ns

    1,00

    32 B

    Max

    .NET 7.0

    10000

    2 948,302 ns

    0,05

    –

    Moyenne

    .NET 6.0

    10000

    52 803,907 ns

    1,00

    32 B

    Moyenne

    .NET 7.0

    10000

    2 967,810 ns

    0,06

    –

    Somme

    .NET 6.0

    10000

    52 732,121 ns

    1,00

    32 B

    Somme

    .NET 7.0

    10000

    5 897,220 ns

    0,11

    –

    Les résultats montrent que le temps d’exécution pour trouver l’élément minimum d’un tableau a généralement diminué de dix fois pour les petits tableaux et de 20 fois pour les tableaux contenant 10 000 éléments. De même, pour les autres méthodes (sauf pour trouver la somme, la différence entre les tailles des collections n’a pas beaucoup affecté les résultats).

    Il convient également de noter que dans .NET 7, aucune mémoire supplémentaire n’est allouée lorsque les méthodes sont appelées.

    Voyons comment ces méthodes fonctionnent avec Liste.

    Méthode

    Durée

    Taille

    Moyenne

    Rapport

    Alloué

    Min

    .NET 6.0

    dix

    122,554 ns

    1,00

    40 B

    Min

    .NET 7.0

    dix

    8,995 ns

    0,07

    –

    Max

    .NET 6.0

    dix

    115,135 ns

    1,00

    40 B

    Max

    .NET 7.0

    dix

    9,171 ns

    0,08

    –

    Moyenne

    .NET 6.0

    dix

    110,825 ns

    1,00

    40 B

    Moyenne

    .NET 7.0

    dix

    8,163 ns

    0,07

    –

    Somme

    .NET 6.0

    dix

    113,812 ns

    1,00

    40 B

    Somme

    .NET 7.0

    dix

    13,197 ns

    0,12

    –

    Min

    .NET 6.0

    10000

    91 529,841 ns

    1,00

    40 B

    Min

    .NET 7.0

    10000

    2 941,226 ns

    0,03

    –

    Max

    .NET 6.0

    10000

    84 565,787 ns

    1,00

    40 B

    Max

    .NET 7.0

    10000

    2 957,451 ns

    0,03

    –

    Moyenne

    .NET 6.0

    10000

    81 205,103 ns

    1,00

    40 B

    Moyenne

    .NET 7.0

    10000

    2 959,882 ns

    0,04

    –

    Somme

    .NET 6.0

    10000

    81 857,576 ns

    1,00

    40 B

    Somme

    .NET 7.0

    10000

    5 783,370 ns

    0,07

    –

    Dans .NET 6, toutes les opérations sur les tableaux sont beaucoup plus rapides que sur les listes. Il en va de même pour les petites collections dans .NET 7. Cependant, à mesure que le nombre d’éléments augmente, les performances des listes sont égales aux tableaux.

    Selon les résultats des tests, les performances des listes ont été multipliées par 31.

    Mais comment cela pourrait-il être réalisé ?

    Examinons de plus près la mise en œuvre de la Min méthode.

    C’est ainsi que le Min méthode est implémentée dans .NET 6 :

    public static int Min(this IEnumerable<int> source)
    {
      if (source == null)
      {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
      }
    
      int value;
      using (IEnumerator<int> e = source.GetEnumerator())
      {
        if (!e.MoveNext())
        {
          ThrowHelper.ThrowNoElementsException();
        }
    
        value = e.Current;
        while (e.MoveNext())
        {
          int x = e.Current;
          if (x < value)
          {
            value = x;
          }
        }
      }
      return value;
    }

    La méthode est assez simple. D’abord, on obtient le IEnumerable collection, prenez l’élément de collection et utilisez le DéplacerSuivant méthode pour obtenir l’élément suivant. Ensuite, nous les comparons, sauvegardons celui qui est le plus petit et répétons jusqu’à la fin de la collection.

    La nouvelle version du Min la méthode est différente :

    public static int Min(this IEnumerable<int> source) => MinInteger(source);

    Les MinEntier La méthode est appliquée à une collection d’entiers. Examinons-le plus en détail.

    private static T MinInteger<T>(this IEnumerable<T> source)
      where T : struct, IBinaryInteger<T>
    {
      T value;
    
      if (source.TryGetSpan(out ReadOnlySpan<T> span))
      {
        if (Vector.IsHardwareAccelerated && 
            span.Length >= Vector<T>.Count * 2)
        {
          .... // Optimized implementation
          return ....;
        }
      }
      .... //Implementation as in .NET 6
    }

    Premièrement, nous essayons d’obtenir l’objet de la ReadOnlySpan type de la collection fournie. Si nous ne parvenons pas à obtenir le ReadOnlySpan représentation de la collection, puis la branche de code (qui correspond à l’implémentation de la Min méthode dans .NET 6) est exécutée. Dans notre cas, on peut obtenir ReadOnlySpanpuisque nous utilisons des tableaux et des listes.

    Mais comment ça ReadOnlySpan, réellement? Les Portée et ReadOnlySpan Les types fournissent une représentation sûre d’une zone de mémoire gérée en continu et non gérée. La structuration de la Portée le type est défini comme un structure de référence. Cela signifie qu’il ne peut être placé que sur la pile, ce qui permet d’éviter d’allouer de la mémoire supplémentaire et améliore les performances des données.

    Les Portée type a également subi quelques modifications dans la nouvelle version de C#. Depuis que C# 11 a introduit la possibilité de créer des champs de référence dans un structure de référencela représentation interne de Portée a changé. Auparavant, un type interne spécial — ParRéférence, a été utilisé pour stocker une référence au début de la zone mémoire, mais il n’y avait pas de contrôle de sécurité. Maintenant champs de référence sont utilisés à cette fin. Il fournit une opération de mémoire plus sécurisée.

    Mais revenons au Min méthode. Quand vous obtenez ReadOnlySpanla méthode tente d’effectuer une recherche vectorielle à l’aide de la Vecteur taper. Pour ce faire, la condition suivante doit être remplie :

    if (Vector.IsHardwareAccelerated && span.Length >= Vector<T>.Count * 2)

    La première partie de la condition vérifie si la propriété Vector.IsHardwareAccelerated renvoie true. Voyons l’implémentation de cette propriété.

    public static bool IsHardwareAccelerated
    {
      [Intrinsic]
      get => false;
    }

    Les [Intrinsic] L’attribut est appliqué au getter. L’attribut indique que la valeur retournée par IsHardwareAccelerated peut être remplacé JIT. La propriété revient vrai si l’accélération matérielle peut être appliquée aux opérations sur les vecteurs grâce à la prise en charge JIT intégrée ; autrement, faux est retourné. Pour activer l’accélération matérielle, vous devez exécuter la génération pour la plate-forme x64 avec la configuration Release ou générer le projet pour AnyCPU avec le paramètre « Préférer 32 bits » désactivé.

    Pour remplir la deuxième partie de la condition, la taille du Portée doit être au moins deux fois la taille du vecteur.

    Comment la taille de ce vecteur est-elle calculée ?

    Les Vecteur le type de données est utilisé dans la nouvelle implémentation du Min méthode pour créer un vecteur. Ce type est un wrapper pour trois autres types : Vecteur64, Vecteur128, et Vecteur256. Ils contiennent des données vectorisées de la longueur correspondante. Vecteur128 peut stocker 16 valeurs 8 bits, huit 16 bits, quatre 32 bits ou deux valeurs 64 bits. Le type…

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.