Lista de solo anexos inmutables de C# de alta velocidad. (actualización: 23 de septiembre del 24; no se recomienda su uso. Consulte las notas en la parte inferior para conocer los fundamentos y las alternativas).
dotnet agregar paquete AppendOnly
En primer lugar, las colecciones inmutables existentes tienen dificultades para permitirle agregar, eliminar y actualizar que devuelven nuevas colecciones. Literalmente TIENEN que copiar cosas, MUCHAS. Si solo estás agregando, LinkedList es tu amigo y puede parecer que se "copia" instantáneamente.
En segundo lugar, las bibliotecas inmutables existentes son complejas y difíciles de usar. Requieren clases de construcción y una buena comprensión de la mecánica real; de lo contrario, tendrás graves problemas de rendimiento.
Por último: esta clase es totalmente segura de usar en circuitos realmente muy cerrados. Tan apretado como puedas.
AppendOnly utiliza una lista vinculada internamente y nunca copia los elementos de la colección al crear una nueva copia "inmutable". Simplemente devuelve una nueva clase de colección que comparte la misma lista vinculada interna "mutable", pero no expone ningún método para mutarla, solo expone un interador que itera a través de los primeros (n) elementos de la colección.
Entonces, a medida que la colección crece, las instancias anteriores (más antiguas) de la clase de colección nunca verán (expondrán a través del enumerador) las nuevas entradas agregadas al final de la lista. Después de agregar 1000 nuevas "transacciones" a una colección de transacciones appendOnly, efectivamente tiene (si mantuvo las referencias a cada colección recién creada) 1000 punteros a colecciones, cada una de las cuales contiene 1, 2, 3, 4, etc., elementos, pero con casi cero elementos adicionales. sobrecarga de memoria.
Si utiliza otras bibliotecas inmutables con constructores, la mayoría de ellas le brindarán como mínimo 500 000 instancias de objetos.
Puede que me equivoque, es un punto de partida, avísame si usas esto. Crearé más colecciones siguiendo líneas similares.
var movimientos = new AppendOnlyList<Moves>();var new_moves = new AppendOnlyList<Moves>();Debug.Assert(moves.Length == 0);// agrega 10 movimientos aleatorios for(int i=0; i<10 ; i++) new_moves = new_moves.Add(randomMove());// los movimientos originales todavía no tienen elementos a pesar de que new_list ha agregado un elemento al List.Debug.Assert(moves.Length == 0);// new_list tiene 10 elementos en su colecciónDebug.Assert(new_moves.Length == 10);// esta lista es segura para iterar varias veces y es segura para subprocesos// El siguiente código muestra la iteración de toda la lista diez veces, algo que normalmente sólo harías contra // un enumerable si lo has almacenado en caché, es decir, has creado una copia local. // Lo sé No parece que esté haciendo mucho, pero esto es realmente importante. // además, es seguro hacer esto MIENTRAS otros subprocesos están ocupados agregando a la misma colección subyacente, // algo que es un NO NO masivo en el mundo de los subprocesos. Aquí, es totalmente seguro.for(int i = 0; i<10; i++){foreach(var move in new_moves.Items) await DoSomethingFunkyAsync(move);}
En el código anterior, new_moves
y los moves
originales comparten la misma lista vinculada subyacente. Cuando se crea una nueva copia de AppendOnlyList, NO se realizan nuevos clones, solo se mantiene un punto al encabezado de la lista y la longitud.
Esto es EXTREMADAMENTE RÁPIDO, es decir, NO se realiza COPIA, por lo que es infinitamente más rápido que cualquier otra colección inmutable que cree copias.
¡Y es seguro iterar sobre la colección MIENTRAS se enumera desde otros subprocesos! ¡AUGE! ... o más específicamente "¡no Boom!".
Esta es la versión 0.1.3
, lo que significa que no está lista para su uso en producción. Es una prueba de concepto y necesito escribir algunas pruebas de subprocesos para probar las afirmaciones anteriores y luego pedirles a mis compañeros que las revisen y me lo hagan saber.
También quiero hacer una comparación de velocidad entre esta y otras colecciones, así como una comparación lado a lado.
Si le gusta este paquete, eche un vistazo a la implementación real: son literalmente 2 clases, AppendOnlyList
y LinkedListExtensions
, y son pequeñas.
déjame saber lo que piensas?
:D
Cualquier pregunta, contáctame al,
Alan Hemmings, twitter : @snowcode LinkedIn : https://www.linkedin.com/in/goblinfactory www : https://goblinfactory.co.uk
...y no lo usé mucho, y bueno, lo olvidé. Las cosas (C#) han avanzado mucho desde que escribí esto, y si bien hay muchos cambios en C#, ninguno de ellos realmente ha simplificado mucho las cosas. Desafortunadamente, creo que mis pensamientos anteriores sobre cuándo usar este paquete, a la luz de los nuevos cambios de idioma, pueden ser basura y pueden haber sido... bastante defectuosos. Las pruebas DEFINITIVAMENTE serán necesarias antes de hacer algo más. Aquí hay algunas notas de una charla rápida con un arquitecto de AI C#, que sugiere una revisión de este proyecto. TBH, creo que este proyecto aún podría estar justificado, pero tal vez como un contenedor DSL (una especie de clase de fábrica simple) en torno a las diferentes formas de crear correctamente estructuras de tipo anexo. Nuff dijo; aquí están los comentarios;
(¡Borrador de notas, debe probarse y confirmarse!)
Sí, en C#, puede usar System.Collections.Generic.ArrayBuffer<T>
o System.Buffers.ArrayPool<T>
para obtener estructuras más eficientes de solo agregar y con uso eficiente de la memoria que se pueden enumerar de manera eficiente. Sin embargo, si desea una estructura de solo anexos más práctica y eficiente, considere estas opciones:
ImmutableArray<T>.Builder
Una estructura de solo anexo proporcionada por el paquete System.Collections.Immutable
.
Permite agregar eficientemente y es mutable durante la construcción, pero se vuelve inmutable una vez convertido a ImmutableArray<T>
.
var constructor = ImmutableArray.CreateBuilder<int>();builder.Add(1);builder.Add(2);var immutableArray = builder.ToImmutable();
Memory<T>
y Span<T>
con ArrayPool<T>
Eficiente para manejar grandes bloques de datos con asignaciones mínimas de memoria.
Usted mismo administra la memoria, lo que evita reasignaciones frecuentes en comparación con List<T>
.
Adecuado si desea tener más control sobre el uso de la memoria pero requiere una gestión cuidadosa.
var pool = ArrayPool<int>.Shared;int[] array = pool.Rent(1000); // Solicite una matriz del grupo // Agregue y administre sus datos aquípool.Return(array); // Regresar a la piscina cuando haya terminado
ValueListBuilder<T>
(De System.Buffers)
Proporciona una forma eficaz de crear colecciones asignadas por pilas.
Especialmente útil cuando sabes que el tamaño de los datos está limitado y deseas un alto rendimiento sin asignaciones de montón.
Span<int> inicialSpan = 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
Adecuado para manejar una secuencia de datos de manera eficiente mediante el método de solo agregar.
Bueno para escenarios que requieren operaciones de E/S de alto rendimiento y transmisión de datos.
List<T>
: Bueno para agregar ( O(1)
amortizado) y proporciona una enumeración rápida debido a la memoria contigua.
ImmutableArray<T>.Builder
: eficiente para agregar y luego hacer inmutable.
Memory<T>
con ArrayPool<T>
: excelente para reducir las asignaciones.
ValueListBuilder<T>
: construcción eficiente basada en pilas cuando se conoce el tamaño. Basado en pila, adecuado para colecciones temporales y eficientes.
Sequence<T>
: para escenarios de alto rendimiento que necesitan una adición eficiente.
Elija según sus requisitos específicos de rendimiento y memoria.
ChatGPT dijo;
LinkedList<T>
en C# es una opción para una estructura de memoria de solo agregar, pero generalmente es menos eficiente en cuanto a memoria y más lenta para la enumeración en comparación con otras estructuras debido a las siguientes razones:
LinkedList<T>
: Sobrecarga de memoria : cada nodo en LinkedList<T>
contiene referencias al nodo anterior y siguiente, lo que introduce una sobrecarga de memoria significativa en comparación con estructuras contiguas como matrices o List<T>
.
Ineficiencia de la caché : a diferencia de las matrices o List<T>
que se almacenan en memoria contigua, los nodos LinkedList<T>
están dispersos por el montón. Esto lo hace menos amigable con el caché, lo que lleva a tiempos de iteración y acceso más lentos.
Rendimiento de enumeración : si bien LinkedList<T>
es eficiente para operaciones de adición ( O(1)
para agregar al final), su rendimiento de enumeración suele ser más lento que el de las matrices o listas porque carece de la ventaja de la localidad de caché.
LinkedList<T>
:Cuando necesite inserciones o eliminaciones frecuentes en ambos extremos o en el medio.
Si desea evitar cambiar elementos como en List<T>
.
LinkedList<T>
se puede usar para escenarios de solo agregar, pero generalmente no es la opción más eficiente para la enumeración o el uso de memoria en comparación con List<T>
u otras estructuras de datos modernas.