Génériques vs listes de tableaux
-
01-07-2019 - |
Question
Le système sur lequel je travaille ici a été écrit avant .net 2.0 et ne bénéficiait pas des avantages des génériques. Il a finalement été mis à jour à la version 2.0, mais aucun code n'a été refactoré en raison de contraintes de temps. Il existe un certain nombre d'endroits où le code utilise des tableaux ArraysLists, etc., qui stockent des éléments sous forme d'objets.
Du point de vue des performances, quelle importance revêt le code d'utiliser des génériques? Je sais que, du point de vue de la performance, de la boxe et du déballage, etc., c'est inefficace, mais quel gain de performance y aura-t-il à la changer? Est-ce que les génériques peuvent être utilisés à l'avenir, ou si le changement de performances est suffisant pour qu'un effort de conscience soit nécessaire pour mettre à jour l'ancien code?
La solution
Techniquement, la performance des génériques est, comme vous le dites, meilleure. Cependant, à moins que les performances ne soient extrêmement importantes ET que vous n'ayez déjà optimisé que dans d'autres domaines, vous obtiendrez BEAUCOUP de meilleures améliorations en passant votre temps ailleurs.
Je suggérerais:
- utiliser les génériques à l'avenir.
- si vous avez des tests unitaires solides, refacturez les génériques lorsque vous touchez le code
- passez plus de temps à effectuer des refactorisations / mesures qui amélioreront considérablement les performances (appels à la base de données, modification des structures de données, etc.) plutôt que quelques millisecondes ici et là.
Bien sûr, il existe des raisons autres que la performance pour passer aux génériques:
- moins sujet aux erreurs, car vous avez la vérification des types à la compilation
- plus lisible, vous n'avez pas besoin de diffuser partout et il est évident que le type est stocké dans une collection
- si vous utilisez des génériques à l'avenir, il est plus propre de les utiliser partout
Autres conseils
Voici les résultats d'une analyse simple d'une chaîne d'un fichier de 100 Ko, 100 000 fois. Il a fallu 612,293 secondes à la liste générique (de caractères) pour parcourir 100 000 fois le fichier. Il a fallu 2,880,415 secondes à ArrayList pour parcourir 100 000 fois le fichier. Cela signifie que dans ce scénario (votre kilométrage variant ), la liste générique (de caractères) est 4,7 fois plus rapide.
Voici le code que j'ai parcouru 100 000 fois:
Public Sub Run(ByVal strToProcess As String) Implements IPerfStub.Run
Dim genList As New ArrayList
For Each ch As Char In strToProcess.ToCharArray
genList.Add(ch)
Next
Dim dummy As New System.Text.StringBuilder()
For i As Integer = 0 To genList.Count - 1
dummy.Append(genList(i))
Next
End Sub
Public Sub Run(ByVal strToProcess As String) Implements IPerfStub.Run
Dim genList As New List(Of Char)
For Each ch As Char In strToProcess.ToCharArray
genList.Add(ch)
Next
Dim dummy As New System.Text.StringBuilder()
For i As Integer = 0 To genList.Count - 1
dummy.Append(genList(i))
Next
End Sub
Le seul moyen de savoir avec certitude consiste à profiler votre code à l'aide d'un outil tel que dotTrace.
http://www.jetbrains.com/profiler/
Il est possible que la boxe / unboxing soit trivial dans votre application particulière et ne mérite pas une refactorisation. A l'avenir, vous devriez toujours envisager d'utiliser des génériques en raison de la sécurité du type à la compilation.
Les génériques, qu’ils soient Java ou .NET, doivent être utilisés pour la sécurité de la conception et des types, pas pour la performance. La substitution automatique est différente des génériques (objet essentiellement implicite pour les conversions de primitives) et, comme vous l'avez mentionné, vous ne devez PAS les utiliser à la place d'une primitive s'il doit y avoir beaucoup d'opérations arithmétiques ou autres qui entraîneront une baisse de performance du résultat répété. création / destruction implicite d'objets.
Dans l’ensemble, je suggérerais d’utiliser l’avenir, et de ne mettre à jour le code existant que s’il doit être nettoyé à des fins de sécurité / conception, et non de performances.
Cela dépend, la meilleure réponse est de profiler votre code et de voir. J'aime AQTime, mais plusieurs packages existent pour cela.
En général, si une liste de tableaux est utilisée BEAUCOUP, il peut être intéressant de la remplacer par une version générique. En réalité, il est fort probable que vous ne puissiez même pas mesurer la différence de performance. La boxe et le déballage sont des étapes supplémentaires, mais les ordinateurs modernes sont si rapides que cela ne fait presque aucune différence. Comme ArrayList n’est en réalité qu’un tableau normal avec un bon wrapper, vous obtiendrez probablement beaucoup plus de performances avec une meilleure sélection de structure de données (ArrayList.Remove is O (n)!) Qu’avec la conversion en génériques.
Edit: Outlaw Programmer a un bon point: vous allez toujours boxer et déballer avec des génériques, cela se produit simplement de manière implicite. Tout le code autour de la vérification des exceptions et des valeurs NULL de la diffusion et "is / as". les mots clés aideraient un peu si.
Les gains les plus importants, vous les trouverez dans les phases de maintenance. Les génériques sont beaucoup plus faciles à gérer et à mettre à jour, sans avoir à traiter de problèmes de conversion et de diffusion. Si c'est le code que vous visitez en permanence, alors prenez l'effort. Si ce code n’a pas été touché depuis des années, cela ne me dérangerait pas vraiment.
Qu'est-ce que l'autoboxing / unboxing a à voir avec les génériques? Ceci est juste une question de sécurité de type. Avec une collection non générique, vous devez explicitement reconvertir le type réel d'un objet. Avec les génériques, vous pouvez ignorer cette étape. Je ne pense pas qu'il y ait une différence de performance d'une manière ou d'une autre.
Mon ancienne société a effectivement examiné ce problème. Notre approche était la suivante: s’il est facile de refactoriser, faites-le; sinon (c’est-à-dire qu’il touchera trop de classes), laissez-le plus tard. Cela dépend vraiment de savoir si vous avez le temps de le faire ou s'il y a des éléments plus importants à coder (c'est-à-dire des fonctionnalités que vous devriez implémenter pour les clients).
Encore une fois, si vous ne travaillez pas sur quelque chose pour un client, allez-y et passez du temps à refactoriser. Cela améliorera la lisibilité du code pour vous-même.
Cela dépend de la quantité de votre code. Si vous liez ou affichez des listes volumineuses dans l'interface utilisateur, vous obtiendrez probablement un grand gain de performances.
Si votre ArrayList est simplement saupoudré ici et là, alors ce ne serait probablement pas un gros problème de le nettoyer, mais cela n’aurait pas non plus un impact important sur les performances globales.
Si vous utilisez beaucoup de tableaux Array dans votre code et qu'il serait extrêmement fastidieux de les remplacer (quelque chose qui peut avoir une incidence sur vos calendriers), vous pouvez adopter une approche si vous le modifiez. .
Cependant, l’essentiel est que les génériques sont beaucoup plus faciles à lire et plus stables dans l’application en raison de leur frappe puissante. Vous constaterez des gains non seulement en termes de performances, mais également en termes de maintenance et de stabilité du code. Si vous pouvez le faire rapidement, je dirais le faire.
Si vous pouvez obtenir l'adhésion du responsable du produit, je vous conseillerais de le nettoyer. Vous aimez plus votre code par la suite.
Si les entités dans ArrayLists sont des types d'objet, vous gagnerez un peu en ne les attribuant pas au type correct. S'il s'agit de types Value (structures ou primitives telles que Int32), le processus de boxing / unboxing ajoute beaucoup de temps système et les collections génériques devraient être beaucoup plus rapides.
Les performances des génériques sont bien meilleures, en particulier si vous utilisez un type de valeur (int, bool, struct, etc.) pour lequel vous obtiendrez un gain de performances remarquable.
-
L'utilisation de Arraylist avec des types de valeur provoque la boxing / unboxing qui, si l'opération est effectuée plusieurs centaines de fois, est considérablement plus lent que l'utilisation de la liste générique.
-
lors du stockage de types de valeur en tant qu'objet, vous aurez jusqu'à quatre fois la mémoire temps par élément. Bien que cette quantité ne videra pas votre RAM, la mémoire cache plus petite peut contenir moins d'éléments. Cela signifie qu'en itérant de longues collections, de nombreuses copies de la mémoire principale vers la mémoire cache ralentiraient votre application.
J'ai écrit à propos de ici .
L'utilisation de génériques signifie également que votre code sera simple et facile à utiliser si vous souhaitez exploiter des éléments tels que linq dans les versions ultérieures de c #.