Estructura contra clases

Estoy a punto de crear 100.000 objetos en el código. Son pequeños, solo que tienen 2 o 3 propiedades. Los pondré en una lista genérica y, cuando lo estén, haré un bucle en ellos y verificará el valor b quizás actualice el valor b .

¿Es más rápido / mejor crear estos objetos como clase o como struct?

EDITAR

a. Las propiedades son tipos de valor (excepto la cadena, ¿no?)

segundo. Podrían (no estamos seguros todavía) tener un método validado

EDIT 2

Me preguntaba: ¿los objetos en el montón y la stack son procesados ​​por igual por el recolector de basura, o eso funciona diferente?

¿Es más rápido crear estos objetos como clase o como estructura?

Usted es la única persona que puede determinar la respuesta a esa pregunta. Pruébelo de ambas formas, mida una métrica de rendimiento relevante, centrada en el usuario y relevante, y luego sabrá si el cambio tiene un efecto significativo en usuarios reales en escenarios relevantes.

Las estructuras consumen menos memoria de stack (porque son más pequeñas y se compactan más fácilmente, no porque estén “en la stack”). Pero tardan más en copiar que una copia de referencia. No sé cuáles son sus métricas de rendimiento para el uso o la velocidad de la memoria; aquí hay una compensación y tú eres la persona que sabe qué es.

¿Es mejor crear estos objetos como clase o como struct?

Tal vez clase, tal vez struct. Como regla general: si el objeto es:
1. Pequeño
2. Lógicamente un valor inmutable
3. Hay muchos de ellos
Entonces consideraría convertirlo en una estructura. De lo contrario, me quedaría con un tipo de referencia.

Si necesita mutar algún campo de una estructura, generalmente es mejor construir un constructor que devuelva una nueva estructura completa con el campo establecido correctamente. Eso es quizás un poco más lento (¡mídelo!) Pero lógicamente mucho más fácil de razonar.

¿Los objetos en el montón y la stack se procesan igualmente por el recolector de basura?

No , no son lo mismo porque los objetos en la stack son las raíces de la colección . El recolector de basura no necesita preguntar “¿está vivo este objeto en la stack?” porque la respuesta a esa pregunta es siempre “Sí, está en la stack”. (Ahora, no puede confiar en eso para mantener vivo un objeto porque la stack es un detalle de implementación. El jitter puede introducir optimizaciones que, digamos, registran lo que normalmente sería un valor de stack, y luego nunca está en la stack por lo que el GC no sabe que todavía está vivo. Un objeto registrado puede hacer que sus descendientes sean recostackdos de manera agresiva, tan pronto como el registro que lo contiene no se vuelva a leer).

Pero el recolector de basura tiene que tratar los objetos en la stack como vivos, de la misma manera que trata a cualquier objeto que se sepa vivo como vivo. El objeto en la stack puede referirse a los objetos asignados en el montón que necesitan mantenerse vivos, por lo que el GC tiene que tratar los objetos de la stack como los objetos vivos que se distribuyen en el montón con el propósito de determinar el conjunto en vivo. Pero, obviamente, no se los trata como “objetos vivos” con el objective de compactar el montón, porque en primer lugar no están en el montón.

¿Está claro?

A veces con struct no necesitas llamar al nuevo () constructor, y asignar directamente los campos, lo que hace que sea mucho más rápido de lo habitual.

Ejemplo:

 Value[] list = new Value[N]; for (int i = 0; i < N; i++) { list[i].id = i; list[i].is_valid = true; } 

es aproximadamente de 2 a 3 veces más rápido que

 Value[] list = new Value[N]; for (int i = 0; i < N; i++) { list[i] = new Value(i, true); } 

donde Value es una struct con dos campos (id y is_valid).

Por otro lado, es necesario mover los elementos o seleccionar los tipos de valor para que todo el proceso de copiado lo ralentice. Para obtener la respuesta exacta, sospecho que tiene que perfilar su código y probarlo.

Una lista devolverá una copia de la estructura. Actualizarlo requeriría eliminarlo de la lista y volverlo a agregar, crear una nueva copia con nuevos valores y asignarla al índice de la lista.

Por lo tanto, es mejor usar clases.

Lectura relacionada: ¿Por qué las estructuras mutables son “malas”?

Las estructuras pueden parecer similares a las clases, pero hay diferencias importantes que debes tener en cuenta. En primer lugar, las clases son tipos de referencia y las estructuras son tipos de valores. Mediante el uso de estructuras, puede crear objetos que se comporten como los tipos incorporados y disfrutar de sus beneficios también.

Cuando llama al operador Nuevo en una clase, se asignará en el montón. Sin embargo, cuando instancia una estructura, se crea en la stack. Esto producirá ganancias de rendimiento. Además, no tratará con referencias a una instancia de una estructura como lo haría con las clases. Trabajará directamente con la instancia struct. Debido a esto, al pasar una estructura a un método, se pasa por valor en lugar de como referencia.

Más aquí:

http://msdn.microsoft.com/en-us/library/aa288471(VS.71).aspx

Las matrices de estructuras se representan en el montón en un bloque contiguo de memoria, mientras que una matriz de objetos se representa como un bloque contiguo de referencias con los objetos reales en otra parte del montón, requiriendo memoria tanto para los objetos como para sus referencias de matriz. .

En este caso, como los está colocando en una List<> (y una List<> está respaldada en una matriz) sería más eficiente, en cuanto a la memoria, usar las estructuras.

(Sin embargo, tenga en cuenta que las grandes matrices encontrarán su camino en el Gran Montículo de Objetos donde, si su duración es larga, puede tener un efecto adverso en la gestión de memoria de su proceso. Recuerde también que la memoria no es la única consideración).

Si tienen semántica de valores, entonces probablemente deberías usar una estructura. Si tienen semántica de referencia, entonces probablemente deberías usar una clase. Hay excepciones, que en su mayoría se inclinan hacia la creación de una clase, incluso cuando hay una semántica de valores, pero comienzan desde allí.

En cuanto a su segunda edición, el GC solo trata con el montón, pero hay mucho más espacio en el montón que el espacio de la stack, por lo que poner las cosas en la stack no siempre es una ganancia. Además de eso, una lista de struct-types y una lista de tipos de clase estará en el montón de cualquier manera, por lo que esto es irrelevante en este caso.

Editar:

Estoy empezando a considerar que el término mal es dañino. Después de todo, hacer una clase mutable es una mala idea si no se necesita activamente, y no descartaría el uso de una estructura mutable. Sin embargo, es una mala idea que casi siempre sea una mala idea, pero sobre todo simplemente no coincide con la semántica de valores, por lo que no tiene sentido utilizar una estructura en el caso dado.

Puede haber excepciones razonables con estructuras privadas anidadas, donde todos los usos de esa estructura están, por lo tanto, restringidos a un scope muy limitado. Esto no aplica aquí sin embargo.

Realmente, creo que “muta, por lo que es un mal conducto”, no es mucho mejor que continuar sobre el montón y la stack (que al menos tiene algún impacto en el rendimiento, incluso si es un error frecuente). “Se muta, por lo que no tiene sentido considerar que tiene semántica de valores, por lo que es una mala estructura” es solo ligeramente diferente, pero lo que es más importante, creo.

La mejor solución es medir, medir nuevamente, y luego medir un poco más. Puede haber detalles de lo que está haciendo que pueden dificultar una respuesta simplificada y fácil como “usar estructuras” o “usar clases”.

Una estructura es, en esencia, nada más ni menos que una agregación de campos. En .NET es posible que una estructura “pretenda” ser un objeto, y para cada tipo de estructura .NET define implícitamente un tipo de objeto de montón con los mismos campos y métodos que, al ser un objeto de montón, se comportará como un objeto . Una variable que contiene una referencia a dicho objeto de montón (estructura “en recuadro”) exhibirá semántica de referencia, pero una que contiene una estructura directamente es simplemente una agregación de variables.

Creo que gran parte de la confusión entre struct-versus-class se debe al hecho de que las estructuras tienen dos casos de uso muy diferentes, que deberían tener pautas de diseño muy diferentes, pero las directrices de MS no distinguen entre ellas. Algunas veces hay una necesidad de algo que se comporte como un objeto; en ese caso, las pautas de MS son bastante razonables, aunque el “límite de 16 bytes” probablemente debería ser más parecido a 24-32. A veces, sin embargo, lo que se necesita es una agregación de variables. Una estructura utilizada para ese propósito debería consistir simplemente en un grupo de campos públicos, y posiblemente una anulación de Equals , una anulación de ToString y una IEquatable(itsType).Equals . Las estructuras que se usan como agregaciones de campos no son objetos ni deberían pretender serlo. Desde el punto de vista de la estructura, el significado del campo debe ser nada más o menos que “lo último escrito en este campo”. Cualquier significado adicional debe ser determinado por el código del cliente.

Por ejemplo, si una estructura agregante de variables tiene miembros Minimum y Maximum , la estructura misma no debe prometer que Minimum <= Maximum . El código que recibe dicha estructura como parámetro debe comportarse como si se pasaran valores Minimum y Maximum separado. Un requisito de que el Minimum no sea mayor que el Maximum debe considerarse como un requisito de que un parámetro Minimum no sea mayor que un Maximum aprobado por separado.

Un patrón útil para considerar a veces es tener una ExposedHolder definida de la siguiente manera:

 class ExposedHolder { public T Value; ExposedHolder() { } ExposedHolder(T val) { Value = T; } } 

Si uno tiene una List> , donde someStruct es una estructura de agregación de variables, uno puede hacer cosas como myList[3].Value.someField += 7; , pero al darle myList[3].Value a otro código le dará el contenido de Value lugar de darle la posibilidad de alterarlo. Por el contrario, si uno usaba List , sería necesario usar var temp=myList[3]; temp.someField += 7; myList[3] = temp; var temp=myList[3]; temp.someField += 7; myList[3] = temp; . Si se utilizara un tipo de clase mutable, la exposición de los contenidos de myList[3] al código externo requeriría copiar todos los campos a algún otro objeto. Si uno utilizara un tipo de clase inmutable, o una estructura de "estilo de objeto", sería necesario construir una nueva instancia que fuera como myList[3] excepción de someField que fuera diferente, y luego almacenar esa nueva instancia en la lista.

Una nota adicional: si está almacenando una gran cantidad de cosas similares, puede ser conveniente almacenarlas en matrices de estructuras posiblemente anidadas, preferiblemente tratando de mantener el tamaño de cada arreglo entre 1K y 64K más o menos. Las matrices de estructuras son especiales, en esa indexación se obtendrá una referencia directa a una estructura interna, por lo que se puede decir "a [12] .x = 5;". Aunque se pueden definir objetos similares a una matriz, C # no les permite compartir dicha syntax con las matrices.

Desde una perspectiva de C ++ estoy de acuerdo en que será más lento modificar las propiedades de una estructura en comparación con una clase. Pero sí creo que serán más rápidos de leer debido a que la estructura se asigna en la stack en lugar del montón. Leer datos del montón requiere más comprobaciones que de la stack.

Usa clases

En una nota general. ¿Por qué no actualizar el valor b cuando los creas?

Bueno, si vas con struct después de todo, deshazte de la cadena y usa un búfer de byte o de tamaño fijo.

Eso es re: rendimiento.