visual studio outofmemoryexception c# .net memory-management out-of-memory .net-micro-framework

c# - studio - ¿Por qué mi conjunto de estructuras ocupa tanta memoria?



system.outofmemoryexception iis (1)

De acuerdo con mis pruebas, supongo que los ValueTypes en Micro Framework no son verdaderos tipos de valores como estamos acostumbrados en el escritorio CLR. Por lo menos, están siendo encasillados. Y puede haber otro nivel de indirección involucrado también. Estos costos se incurren en una sobrecarga de memoria (bastante sustancial para una plataforma incrustada).

Voy a convertir a un int[] en mi FixedSizedQueue .

En realidad, terminé usando UInt32[] y agregué algunos métodos de extensión para ajustar el ataque de bits.

Busqué un poco en el código fuente , pero no pude encontrar nada útil (y tampoco sé qué buscar).

Pregunta: ¿Cómo asigna el Micro Framework memoria para un conjunto de estructuras?

Repositorio de BitBucket con código para replicar.

Contexto y detalle

Estoy haciendo una cola usando una matriz de tamaño fijo para insertar retrasos en el procesamiento de las pulsaciones de teclado desde un teclado USB. Estoy usando una struct para representar los eventos de subir y bajar la tecla y la demora.

public struct QueuedEvent { public readonly EventType Type; // Byte public readonly byte KeyPressed; public readonly TinyTimeSpan Delay; // Int16 public readonly static QueuedEvent Empty = new QueuedEvent(); } public enum EventType : byte { None = 0, Delay = 1, KeyDown = 2, KeyUp = 3, KeyPress = 4, } public class FixedSizeQueue { private readonly QueuedEvent[] _Array; private int _Head = 0; private int _Tail = 0; public FixedSizeQueue(int size) { _Array = new QueuedEvent[size]; } // Enqueue and Dequeue methods follow. }

Hubiera pensado que mi QueuedEvent ocuparía 4 bytes en la memoria, pero, en función de la salida de depuración del recolector de basura (específicamente los tipos VALUETYPE y SZARRAY ), ¡en realidad ocupa 84 bytes cada uno! ¡Esto me parece excesivo! (Y realmente parece ser de 84 bytes cada uno, porque obtengo una OutOfMemoryException si OutOfMemoryException 512 de ellos. Tengo ~ 20kB de RAM disponible, por lo que debería poder asignar 512 fácilmente).

Pregunta (de nuevo): ¿Cómo gestiona Micro Framework la asignación de 84 bytes para una estructura que podría caber en 4?

Recolectores de Basura

Aquí hay una tabla de matrices de diferentes tamaños de QueuedEvent (después de restar las cantidades cuando QueuedEvent 0):

+--------+-----------+-----------+---------+------------+-------+ | Number | VALUETYPE | B/Q''dEvnt | SZARRAY | B/Q''edEvnt | Total | | 16 | 1152 | 72 | 192 | 12 | 84 | | 32 | 2304 | 72 | 384 | 12 | 84 | | 64 | 4608 | 72 | 768 | 12 | 84 | | 128 | 9216 | 72 | 1536 | 12 | 84 | +--------+-----------+-----------+---------+------------+-------+

Según los números de SZARRAY , supongo que mis campos QueuedEvent se alinean con los límites de Int32, ocupando así 12 bytes. Pero no tengo idea de dónde provienen los 72 bytes adicionales.

Editar: Debug.GC(true) estos números llamando a Debug.GC(true) y observando el volcado que recibo en mi salida del depurador. No he encontrado una referencia que identifique exactamente lo que significa cada uno de los números.

Me doy cuenta de que simplemente podría asignar un int[] , pero eso significa que pierdo la buena encapsulación y cualquier tipo de seguridad de la estructura. Y realmente me gustaría saber cuál es el verdadero costo de una estructura en el micro framework.

Mi TinyTimeSpan es muy parecido a un TimeSpan normal, excepto que está usando un Int16 para representar un número de milisegundos en lugar de un Int64 que representa 100ns tics.

public struct TinyTimeSpan { public static readonly TinyTimeSpan Zero = new TinyTimeSpan(0); private short _Milliseconds; public TinyTimeSpan(short milliseconds) { _Milliseconds = milliseconds; } public TinyTimeSpan(TimeSpan ts) { _Milliseconds = (short)(ts.Ticks / TimeSpan.TicksPerMillisecond); } public int Milliseconds { get { return _Milliseconds; } } public int Seconds { get { return _Milliseconds * 1000; } } }

Estoy usando un FEZ Domino como hardware. Es totalmente posible que esto sea específico del hardware. Además, Micro Framework 4.1.

Editar - Más pruebas y comentarios Respuestas

QueuedEvent muchas más pruebas (en el emulador esta vez, no en hardware real, pero los números para QueuedEvent son los mismos, así que supongo que mi hardware sería idéntico para otras pruebas).

Repositorio de BitBucket con código para replicar.

Los siguientes tipos y estructuras integrales no atraen ninguna sobrecarga como VALUETYPE :

  • Byte (1 byte)
  • Int32 (4 bytes)
  • Int16 (2 bytes)
  • Int64 (8 bytes)
  • Doble (8 bytes)
  • TimeSpan (12 bytes, extraño, ya que su miembro interno es un Int64)
  • DateTime (12 bytes - extraño)

Sin embargo, Guid hace: cada uno usa 36 bytes.

El miembro estático vacío asigna VALUETYPE , utilizando 72 bytes (12 bytes menos que la misma estructura en una matriz).

Asignar la matriz como miembro static no cambia nada.

Ejecutar en modos de depuración o liberación no hace diferencia. Sin embargo, no sé cómo obtener la información de depuración de GC sin un depurador adjunto. Pero se interpreta Micro Framework, por lo que no sé qué efecto tendría de todos modos un depurador no conectado.

Micro Framework no admite código unsafe . Tampoco es compatible con StructLayout Explicit (bueno, técnicamente lo hace, pero no hay ningún atributo FieldOffset ). StructLayout Auto y Sequential no hacen diferencia.

Aquí hay algunas estructuras más y su asignación de memoria medida:

// Uses 12 bytes in SZARRAY and 24 in VALUETYPE, total = 36 each public struct JustAnInt32 { public readonly Int32 Value; } // Uses 12 bytes in SZARRAY and 48 in VALUETYPE, total = 60 each // Same as original QueuedEvent but only uses integral types. public struct QueuedEventSimple { public readonly byte Type; public readonly byte KeyPressed; public readonly short DelayMilliseconds; // Replacing the short with TimeSpan does not change memory usage. } // Uses 12 bytes in SZARRAY and 12 in VALUETYPE, total = 24 each // I have to admit 24 bytes is a bit much for an empty struct!! public struct Empty { }

Parece que cada vez que uso una estructura personalizada, incurro en algún tipo de sobrecarga. Y no importa lo que incluya en la estructura, siempre requiere 12 bytes en SZARRAY . Así que probé esto:

// Uses 12 bytes in SZARRAY and 36 in VALUETYPE, total = 48 each public struct DifferentEntity { public readonly Double D; public readonly TimeSpan T; } // Uses 12 bytes in SZARRAY and 108 in VALUETYPE, total = 120 each public struct MultipleEntities { public readonly DifferentEntity E1; public readonly DifferentEntity E2; } // Uses 12 bytes in SZARRAY and 60 in VALUETYPE, total = 72 each // This is equivalent to MultipleEntities, but has quite different memory usage. public struct TwoDoublesAndTimeSpans { public readonly double D1; public readonly TimeSpan T1; public readonly double D2; public readonly TimeSpan T2; }

Edición menor

Después de publicar mi propia respuesta, me di cuenta de que siempre había una sobrecarga de 12 bytes en SZARRAY por artículo. Así que probé un object[] . Los tipos de referencia consumen 12 bytes cada uno en Micro Framework.

Una struct public struct Empty { } consume 24 bytes cada uno.