2010-10-15 15 views
80

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, los buscaré y verificaré el valor a y quizás actualice el valor b.Estructuras versus clases

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

EDITAR

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

b. Podrían (no estamos seguros todavía) tienen un método de validación

EDIT 2

Me preguntaba: son objetos de la pila y la pila procesado a partes iguales por el recolector de basura, o hace que el trabajo diferente ?

+2

¿Van a tener campos públicos o también van a tener métodos? ¿Son los tipos tipos primitivos, como los enteros? ¿Se incluirán en una matriz, o en algo como List ? – JeffFerguson

+0

En caso de duda use una clase. Si necesita la inicialización automática en una matriz, use una estructura. – leppie

+13

¿Una lista de estructuras mutables? Cuidado con el velociraptor. –

Respuesta

119

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

Usted es la única persona que puede determinar la respuesta a esa pregunta. Inténtelo de ambas formas, mida una medida 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 pila (porque son más pequeñas y se compactan más fácilmente, no porque estén "en la pila"). 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 para crear estos objetos como clase o como struct?

Maybe class, maybe struct. Como regla general: Si el objeto es:
1. Pequeño
2. Lógicamente un valor inmutable
3. Hay una gran cantidad de ellos
Entonces me gustaría considerar lo que es una estructura. De lo contrario, me quedaría con un tipo de referencia.

Si necesita modificar algún campo de una estructura, generalmente es mejor construir un constructor que devuelva una estructura nueva 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 pila se procesan igualmente por el recolector de basura?

Sin, no son lo mismo porque objetos en la pila son las raíces de la colección. El recolector de basura no necesita preguntar "¿está vivo este objeto en la pila?" porque la respuesta a esa pregunta es siempre "Sí, está en la pila". (Ahora, no puede confiar en que mantenga un objeto vivo porque la pila es un detalle de implementación. El jitter puede introducir optimizaciones que, digamos, registran lo que normalmente sería un valor de pila, y luego nunca está activado la pila para que el GC no sepa que todavía está viva. Un objeto registrado puede recoger sus descendientes de manera agresiva, tan pronto como el registro que lo contiene no se lea nuevamente)

Pero el recolector de basura does tiene que tratar los objetos en la pila como vivos, de la misma manera que trata a cualquier objeto que se sepa vivo como vivo. El objeto en la pila 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 pila como los objetos vivos que se distribuyen en el montón con el propósito de determinar el conjunto en vivo. Pero obviamente son no tratados como "objetos en vivo" con el objetivo de compactar el montón, porque en primer lugar no están en el montón.

¿Está claro?

+0

Eric, ¿sabe si el compilador o el jitter hacen uso de la inmutabilidad (quizás si se aplica con 'readonly') para permitir optimizaciones. No dejaría que eso afectara la elección de la mutabilidad (en teoría soy un fanático de los detalles de la eficiencia, pero en la práctica mi primer paso hacia la eficiencia siempre es intentar tener la más simple garantía de corrección posible y, por lo tanto, no tener que desperdiciar ciclos de CPU y ciclos cerebrales en cheques y cajas de borde, y ser apropiadamente mutable o inmutable ayuda allí), pero contrarrestaría cualquier reacción instintiva a que diga que la inmutabilidad puede ser más lenta. –

+0

@Jon: el compilador de C# optimiza los datos de * const * pero no * de solo lectura *.No sé si el compilador jit realiza optimizaciones de almacenamiento en caché en campos de solo lectura. –

+0

Una pena, ya que sé que el conocimiento de la inmutabilidad permite algunas optimizaciones, pero alcanza los límites de mi conocimiento teórico en ese punto, pero son límites que me encantaría estirar. Mientras tanto "puede ser más rápido en ambos sentidos, aquí está el por qué, ahora prueba y averigua qué aplica en este caso" es útil para poder decir :) –

18

Una lista devolverá una copia de la estructura. Actualizarlo requeriría sacándolo de la lista y volviendo a agregar, , creando una nueva copia con nuevos valores y asignándola al índice de la lista.

Por lo tanto, es mejor usar las clases.

Lectura relacionada: Why are mutable structs “evil”?

+5

Es exactamente por eso que debe usar matrices con tipos de valores, para evitar dicha copia :) – leppie

+0

@GSerg tiene razón, también las estructuras generalmente deben tener menos de 16 bytes, si una estructura es más grande que 16 bytes, podría considerar convertirla en una clase. – TheLukeMcCarthy

+0

+1 para el .... enlace malvado y +1 para eliminar y agregar información – Michel

1

Use clases.

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

6

Las estructuras pueden parecer similares a las clases, pero hay diferencias importantes que debe 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 pila. 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

+4

Sé que lo dice en MSDN, pero MSDN no cuenta toda la historia. Stack vs. Heap es un detalle de implementación y las estructuras no * siempre * van en la pila. Para un blog reciente sobre este tema, consulte: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx –

+0

"... se aprobó por valor ... "ambas referencias y estructuras pasan por valor (a menos que uno use 'ref') - es si se pasa un valor o referencia que difiere, es decir, las estructuras se pasan valor por valor, los objetos de clase se pasan referencia por valor y los parámetros marcados con referencia pasan referencia por referencia. –

+9

Ese artículo es engañoso en varios puntos clave, y le pedí al equipo de MSDN que lo revise o lo elimine. –

6

Arrays 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 mismos en otro lugar objetos reales en el montón, lo que requiere memoria para los objetos y para sus referencias de matriz.

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

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

+0

Puede usar la palabra clave 'ref' para tratar esto. – leppie

+0

"Sin embargo, tenga en cuenta que las grandes matrices encontrarán su camino en el Heap Large Object Heap donde, si su vida útil es larga, puede tener un efecto adverso en la gestión de la memoria de su proceso". - No estoy seguro de por qué piensas eso? Ser asignado en el LOH no causará ningún efecto adverso en la administración de la memoria a menos que (posiblemente) sea un objeto efímero y desee reclamar la memoria rápidamente sin esperar una colección Gen 2. –

+0

@Jon Artus: el LOH no se compacta. Cualquier objeto de larga vida dividirá el LOH en el área de memoria libre antes y después del área. Se requiere memoria contigua para la asignación y si estas áreas no son lo suficientemente grandes para una asignación, se asigna más memoria al LOH (es decir, obtendrá la fragmentación LOH). –

3

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".

+0

de acuerdo con la parte de la medida, pero en mi opinión fue un ejemplo directo y claro, y pensé que tal vez podrían decirse cosas genéricas al respecto. Y como resultó, algunas personas lo hicieron. – Michel

4

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, la GC solo trata con el montón, pero hay mucho más espacio en el montón que el espacio de la pila, por lo que poner las cosas en la pila 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 el término mal a ser perjudicial. 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 anidadas privadas, donde todos los usos de esa estructura están por lo tanto restringidos a un alcance muy limitado. Esto no aplica aquí sin embargo.

Realmente, creo que "muta de modo que es un mal conducto" no es mucho mejor que continuar con el montón y la pila (que al menos tiene algún impacto en el rendimiento, incluso si es un error frecuente). "Muta, por lo que es muy probable que no tenga 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.

3

Una estructura es, en esencia, nada más y nada 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 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 fin debería consistir simplemente en un grupo de campos públicos, y posiblemente una anulación Equals, una anulación ToString y una implementación 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 en sí 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 por separado. Un requisito de que Minimum no sea mayor que Maximum debe considerarse como un requisito de que un parámetro Minimum no sea mayor que uno Maximum por separado.

Un modelo útil tener en cuenta a veces es tener una ExposedHolder<T> clase definida algo como:

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

Si uno tiene un List<ExposedHolder<someStruct>>, donde someStruct es una estructura variable de agregación, uno puede hacer cosas como myList[3].Value.someField += 7;, pero dando myList[3].Value a otro código le dará el contenido de Value en lugar de darle la posibilidad de alterarlo. Por el contrario, si uno usara un List<someStruct>, sería necesario usar var temp=myList[3]; temp.someField += 7; myList[3] = temp;. Si se utilizara un tipo de clase mutable, exponer el contenido de myList[3] al código externo requeriría copiar todos los campos a algún otro objeto. Si se usara un tipo de clase inmutable, o una estructura de "estilo de objeto", sería necesario construir una nueva instancia que fuera como myList[3], excepto por someField, que era 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 matriz 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 sintaxis con las matrices.

2

Desde una perspectiva 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 pila en lugar del montón. Leer datos del montón requiere más comprobaciones que de la pila.

20

A veces, con struct no necesita llamar al constructor new() y asignar directamente los campos haciéndolo 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 vaya a ralentizar. Para obtener la respuesta exacta, sospecho que tiene que perfilar su código y probarlo.

+2

+1 para mostrar un claro ejemplo – leppie

+0

Obviamente, las cosas se vuelven mucho más rápidas cuando se marcan valores sobre los límites nativos también. – leppie

+0

+1 para el ejemplo – Michel

1

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

Eso es re: rendimiento.

Cuestiones relacionadas