Liste d'ajout uniquement immuable C# haute vitesse. (mise à jour : 23 septembre 24 ; utilisation non recommandée. Voir les notes en bas pour la justification et les alternatives.)
dotnet ajouter le package AppendOnly
Premièrement, les collections immuables existantes ont beaucoup de mal à vous permettre d'ajouter, de supprimer et de mettre à jour qui renvoient de nouvelles collections. Ils DOIVENT littéralement copier des trucs, BEAUCOUP. Si vous ajoutez uniquement, LinkedList est votre ami et peut sembler « copié » instantanément.
Deuxièmement, les bibliothèques immuables existantes sont complexes et difficiles à utiliser. Ils nécessitent des cours de constructeur et une bonne compréhension de la vraie mécanique, sinon vous aurez de sérieux problèmes de performances.
Enfin : cette classe est totalement sûre à utiliser dans des boucles vraiment très serrées. Comme dedans, aussi serré que possible.
AppendOnly utilise A linkedList en interne et ne copie jamais les éléments de la collection lors de la création d'une nouvelle copie « immuable ». Il renvoie simplement une nouvelle classe de collection qui partage la même liste liée "mutable" interne, mais n'expose aucune méthode pour la muter, exposant uniquement un interateur qui parcourt les (n) premiers éléments de la collection.
Ainsi, à mesure que la collection s'agrandit, les instances antérieures (plus anciennes) de la classe de collection ne verront jamais (exposeront via l'énumérateur) les nouvelles entrées ajoutées à la fin de la liste. Après avoir ajouté 1 000 nouvelles "transactions" à une appendOnly transactionCollection, vous disposez effectivement (si vous avez conservé les références à chaque collection nouvellement créée) de 1 000 pointeurs vers des collections contenant chacune 1, 2, 3, 4, etc., mais avec près de zéro élément supplémentaire. surcharge de mémoire.
Si vous utilisez les autres bibliothèques immuables avec des constructeurs, la plupart d'entre elles vous donneront au minimum 500 000 instances d'objets.
Je me trompe peut-être, c'est un point de départ, faites-moi savoir si vous l'utilisez. Je vais créer d'autres collections dans le même sens.
var move = new AppendOnlyList<Moves>();var new_moves = new AppendOnlyList<Moves>();Debug.Assert(moves.Length == 0);// ajouter 10 mouvements aléatoires pour (int i=0; i<10 ; i++) new_moves = new_moves.Add(randomMove());// les mouvements originaux n'ont toujours aucun élément même si new_list a ajouté un élément au List.Debug.Assert(moves.Length == 0);// new_list a 10 éléments dans sa collectionDebug.Assert(new_moves.Length == 10);// cette liste peut être itérée plusieurs fois en toute sécurité et est threadsafe// le code ci-dessous montre une itération sur toute la liste dix fois, quelque chose que vous ne feriez normalement que contre // un énumérable si vous l'avez mis en cache, c'est-à-dire créé une copie locale. // Je sais que ce n'est pas le cas on dirait que ça fait beaucoup, mais c'est vraiment important. // également, il est possible de le faire en toute sécurité PENDANT que d'autres threads sont occupés à ajouter à la même collection sous-jacente, // quelque chose qui est un énorme NON NON dans le monde des threads. Ici, c'est totalement sûr.for(int i = 0; i<10; i++){foreach(var move in new_moves.Items) wait DoSomethingFunkyAsync(move);}
Dans le code ci-dessus, new_moves
et les moves
d'origine partagent la même liste chaînée sous-jacente. Lorsqu'une nouvelle copie de AppendOnlyList est créée, AUCUN nouveau clone n'est créé, seul un point vers l'en-tête de la liste et la longueur sont conservés.
C'est EXTRÊMEMENT RAPIDE, car AUCUNE COPIE n'est effectuée, c'est donc infiniment plus rapide que toute autre collection immuable qui crée des copies.
Et il est thread-safe de parcourir la collection PENDANT qu'elle est énumérée à partir d'autres threads ! BOOM! ... ou plus précisément "no Boom!".
il s'agit de la version 0.1.3
ce qui signifie qu'elle n'est pas prête pour une utilisation en production. C'est une preuve de concept, et je dois écrire quelques tests de threading pour prouver les affirmations ci-dessus, puis demander à mes pairs de les examiner et de me le faire savoir.
Je souhaite également faire une comparaison de vitesse entre cette collection et d'autres collections ainsi qu'une comparaison côte à côte.
si vous aimez ce package, jetez un œil à l'implémentation réelle, il s'agit littéralement de 2 classes, AppendOnlyList
et LinkedListExtensions
, et elles sont minuscules.
dis-moi ce que tu en penses ?
:D
Des questions, contactez-moi au,
Alan Hemmings, twitter : @snowcode LinkedIn : https://www.linkedin.com/in/goblinfactory www : https://goblinfactory.co.uk
... et je ne l'ai pas beaucoup utilisé, et bien, je l'ai oublié. Les choses (C#) ont beaucoup évolué depuis que j'ai écrit ceci, et bien qu'il y ait beaucoup de changements en C#, aucun d'entre eux n'a vraiment simplifié les choses. Malheureusement, je pense que mes réflexions ci-dessus, sur le moment d'utiliser ce package, à la lumière des nouveaux changements de langue, pourraient être nulles et pourraient avoir été... assez défectueuses. Les tests sont CERTAINEMENT nécessaires avant de faire quoi que ce soit d'autre. Voici quelques notes d'une discussion rapide avec un architecte AI C#, suggérant une révision de ce projet. TBH, je pense que ce projet pourrait encore être justifié, mais peut-être en tant que wrapper DSL (sorte de classe d'usine simple) autour des différentes façons de créer correctement des structures de type appendice. Nuff dit : voici les commentaires ;
(projet de notes, doit être testé et confirmé !)
Oui, en C#, vous pouvez utiliser System.Collections.Generic.ArrayBuffer<T>
ou System.Buffers.ArrayPool<T>
pour des structures plus efficaces en ajout uniquement et en mémoire qui peuvent être énumérées efficacement. Cependant, si vous souhaitez une structure d'ajout uniquement plus pratique et plus efficace, envisagez ces options :
ImmutableArray<T>.Builder
Une structure en ajout uniquement fournie par le package System.Collections.Immutable
.
Il permet un ajout efficace et est modifiable lors de la construction, mais devient immuable une fois converti en ImmutableArray<T>
.
var builder = ImmutableArray.CreateBuilder<int>();builder.Add(1);builder.Add(2);var immutableArray = builder.ToImmutable();
Memory<T>
et Span<T>
avec ArrayPool<T>
Efficace pour gérer des blocs de données volumineux avec des allocations de mémoire minimales.
Vous gérez vous-même la mémoire, ce qui évite les réallocations fréquentes par rapport à List<T>
.
Convient si vous souhaitez plus de contrôle sur l’utilisation de la mémoire mais nécessitez une gestion minutieuse.
var pool = ArrayPool<int>.Shared;int[] array = pool.Rent(1000); // Demander un tableau au pool // Ajoutez et gérez vos données icipool.Return(array); // Retour à la piscine une fois terminé
ValueListBuilder<T>
(à partir de System.Buffers)
Fournit un moyen efficace de créer des collections de manière allouée par pile.
Particulièrement utile lorsque vous savez que la taille des données est limitée et que vous souhaitez des performances élevées sans allocations de tas.
Span<int> initialSpan = stackalloc int[16]; var valueListBuilder = new ValueListBuilder<int>(initialSpan);valueListBuilder.Append(1);valueListBuilder.Append(2);ReadOnlySpan<int> span = valueListBuilder.AsSpan();
Sequence<T>
de System.IO.Pipelines
Adapté pour gérer efficacement une séquence de données en ajoutant uniquement.
Idéal pour les scénarios nécessitant des opérations d’E/S hautes performances et des données en streaming.
List<T>
: Idéal pour l'ajout ( O(1)
amorti) et fournit une énumération rapide en raison de la mémoire contiguë.
ImmutableArray<T>.Builder
: efficace pour ajouter puis rendre immuable.
Memory<T>
avec ArrayPool<T>
: Idéal pour réduire les allocations.
ValueListBuilder<T>
: construction efficace basée sur la pile lorsque la taille est connue. Basé sur une pile, adapté aux collections temporaires et efficaces.
Sequence<T>
: pour les scénarios à haut débit nécessitant un ajout efficace.
Choisissez en fonction de vos besoins spécifiques en matière de performances et de mémoire.
ChatGPT a dit :
LinkedList<T>
en C# est une option pour une structure de mémoire à ajout uniquement, mais elle est généralement moins efficace en mémoire et plus lente pour l'énumération par rapport aux autres structures pour les raisons suivantes :
LinkedList<T>
: Surcharge de mémoire : chaque nœud d'un LinkedList<T>
contient des références aux nœuds suivants et précédents, ce qui introduit une surcharge de mémoire importante par rapport aux structures contiguës comme les tableaux ou List<T>
.
Inefficacité du cache : contrairement aux tableaux ou List<T>
qui sont stockés dans la mémoire contiguë, les nœuds LinkedList<T>
sont dispersés sur le tas. Cela le rend moins convivial pour le cache, ce qui entraîne des temps d'itération et d'accès plus lents.
Performances d'énumération : bien que LinkedList<T>
soit efficace pour les opérations d'ajout ( O(1)
pour ajouter à la fin), ses performances d'énumération sont généralement plus lentes que celles des tableaux ou des listes car elles ne bénéficient pas de l'avantage de la localité du cache.
LinkedList<T>
:Lorsque vous avez besoin d’insertions ou de suppressions fréquentes aux deux extrémités ou au milieu.
Si vous souhaitez éviter de déplacer des éléments comme dans List<T>
.
LinkedList<T>
peut être utilisé pour des scénarios d'ajout uniquement, mais n'est généralement pas le choix le plus efficace pour l'énumération ou l'utilisation de la mémoire par rapport à List<T>
ou à d'autres structures de données modernes.