funciones - ¿Por qué 16 bytes es el tamaño recomendado para struct en C#?
struct c# (7)
Leí el libro de Cwalina (recomendaciones sobre desarrollo y diseño de aplicaciones .NET).
Él dice que una estructura bien diseñada tiene que tener menos de 16 bytes de tamaño (por motivos de rendimiento).
¿Por qué exactamente es esto?
Y (más importante) ¿puedo tener struct más grande con la misma eficacia si ejecuto mi aplicación de .NET 3.5 (que pronto será .NET 4.0) de 64 bits en Core i7 en Windows 7 x64 (¿está basada en esta limitación CPU / OS)?
Solo para estresar nuevamente, necesito una estructura tan eficiente como sea posible. Intento mantenerlo en la pila todo el tiempo. La aplicación tiene muchos subprocesos múltiples y se ejecuta en intervalos de menos de milisegundos, y el tamaño actual de la estructura es de 64 bytes.
Creo que otro punto clave es que si tiene una lista con un millón de estructuras, esa es solo una asignación de memoria en el montón, mientras que una lista con un millón de clases tendrá aproximadamente un millón de objetos de montón separados. La carga de recolección de basura es algo bastante diferente en ambos casos. Desde este punto de vista, la estructura puede salir adelante.
El compilador JIT produce un código de caso especial para copiar estructuras que son más pequeñas que ciertos umbrales, y una construcción de propósito general algo más lenta para las más grandes. Conjeturo que cuando se escribió ese consejo, el umbral era de 16 bytes, pero en el marco actual de 32 bits parece ser de 24 bytes; puede ser más grande para el código de 64 bits.
Habiendo dicho eso, el costo de crear cualquier objeto de clase de tamaño es sustancialmente mayor que el costo de copiar una estructura que contiene los mismos datos. Si el código crea un objeto de clase con un valor de 32 bytes de campos y luego pasa o copia una referencia a ese objeto 1,000 veces, el ahorro de tiempo de copiar 1,000 referencias de objetos en lugar de tener que copiar 1,000 estructuras de 32 bytes probablemente supere el costo de creando el objeto de clase. Sin embargo, si la instancia del objeto se abandonara después de que la referencia se haya copiado solo dos veces, el costo de crear el objeto probablemente excedería por un margen grande el costo de copiar una estructura de 32 bytes dos veces.
Tenga en cuenta también que, en muchos casos, es posible evitar eludir las estructuras por valor o copiarlas de forma redundante, si se intenta hacerlo. Pasar cualquier tamaño de estructura como un parámetro ref
a un método, por ejemplo, solo requiere pasar una dirección de una sola máquina-palabra (4 u 8 bytes). Debido a que .net carece de cualquier tipo de concepto de const ref
, solo los campos o variables escribibles pueden pasarse de esa manera, y las colecciones integradas en .net -excepto System.Array
no proporcionan medios para acceder a los miembros por ref
. Sin embargo, si uno está dispuesto a usar matrices o colecciones personalizadas, incluso estructuras enormes (100 bytes o más) pueden procesarse de manera muy eficiente. En muchos casos, la ventaja de rendimiento de usar una estructura en lugar de una clase inmutable puede crecer con el tamaño de los datos encapsulados.
En realidad, en una máquina de 64 bits, 32 bytes es el tamaño máximo que se debe usar para los datos de estructura. A partir de mis pruebas, la estructura de 32 bytes (cuatro largos) tiene aproximadamente el mismo efecto que una referencia de objeto.
Puede encontrar el código de prueba y ejecutar las pruebas usted mismo aquí: https://github.com/agileharbor/structBenchmark
Las últimas dos pruebas copian una gran cantidad de estructuras / objetos de una matriz a otra, y usted puede ver dónde la clase supera la estructura con ocho largos, pero es más o menos lo mismo que la estructura con cuatro largos.
Estás citando erróneamente el libro (al menos la 2ª edición). Jeffrey Richter afirma que los tipos de valores pueden tener más de 16 bytes si:
No tiene la intención de pasarlos a otros métodos o copiarlos desde y hacia una clase de colección.
Además Eric Gunnerson agrega (con respecto al límite de 16 bytes)
Use esta guía como un disparador para hacer más investigación.
Simplemente no es cierto que una estructura "tiene que tener menos de 16 bytes de tamaño". Todo depende del uso.
Si está creando la estructura y también la está consumiendo y le preocupa el rendimiento, utilice un generador de perfiles que compare una estructura o clase para ver cuál funciona mejor para usted.
Solo usted sabe cómo se usan sus estructuras en su programa. Pero si nada más, siempre puedes probarlo por ti mismo. Por ejemplo, si se pasa con frecuencia a otras funciones, lo siguiente puede iluminarlo:
class MainClass
{
static void Main()
{
Struct64 s1 = new Struct64();
Class64 c1 = new Class64();
DoStuff(s1);
DoStuff(c1);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10000; i++)
{
s1 = DoStuff(s1);
}
sw.Stop();
Console.WriteLine("Struct {0}", sw.ElapsedTicks);
sw.Reset();
sw.Start();
for (int i = 0; i < 10000; i++)
{
c1 = DoStuff(c1);
}
sw.Stop();
Console.WriteLine("Class {0}", sw.ElapsedTicks);
sw.Reset();
Console.ReadLine();
}
}
con:
public class Class64
{
public long l1;
public long l2;
public long l3;
public long l4;
public long l5;
public long l6;
public long l7;
public long l8;
}
public struct Struct64
{
public long l1;
public long l2;
public long l3;
public long l4;
public long l5;
public long l6;
public long l7;
public long l8;
}
Pruebe este tipo de cosas con estructuras / clases representativas y vea qué resultados obtiene. (En mi máquina, prueba anterior, la clase parece ~ 3 veces más rápida)
Una de las cosas que aprendí durante la programación en lenguaje ensamblador fue que alineas tus estructuras (y otros datos) en límites de 8 o 16 bytes (en realidad hay un comando de preprocesador en MASM). La razón de esto es un acceso a la memoria considerablemente más rápido al almacenar o mover cosas en la memoria. Mantener las estructuras de menos de 16 bytes garantizaría que la alineación de la memoria se realice con éxito, ya que nada cruzaría los límites de la alineación.
Sin embargo, esperaría que este tipo de cosas se ocultaran en gran parte al usar el framework .NET. Si sus estructuras fueran más grandes que 16 bytes, entonces esperaría que el marco (o el compilador JIT) alinee sus datos en los límites de 32 bytes, y así sucesivamente. Sería interesante ver alguna validación de los comentarios en esos libros, y ver cuál es la diferencia en velocidad.
Una estructura más grande no es tan eficiente, pero luego ... si TIENES más datos, la tienes. No tiene sentido hablar de eficiencia allí.
64 bytes deberían estar bien.
La razón principal es posiblemente las operaciones de copia ... que hacen que IIRC sea más lento si la estructura es más grande. Y debe ser copiado bastante.
Normalmente aconsejaría usar una clase aquí;) Pero sin conocer el contenido de la estructura, es un poco complicado.