Вопрос: Как Micro Framework выделяет память для массива структур?
репозиторий BitBucket с реплицируемым кодом.
Контекст и деталь
Я делаю очередь, используя массив фиксированного размера, чтобы вставлять задержки при обработке нажатий клавиш с USB-клавиатуры. Я использую struct
для представления событий вверх и вниз и задержки.
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.
}
Я бы подумал, что мой QueuedEvent
будет занимать байты 4 в памяти, но, основываясь на просмотре вывода отладки сборщика мусора (в частности, типов VALUETYPE
и SZARRAY
), он фактически занимает 84 байтов каждый! Это меня поражает! (И это, по-видимому, составляет 84 байта, потому что я получаю OutOfMemoryException
, если я выделяю 512 из них. У меня есть ~ 20 КБ ОЗУ, поэтому я мог бы легко выделить 512.
Вопрос (снова):. Как Micro Framework удается выделить 84 байта для структуры, которая может поместиться в 4?
Данные сборщика мусора
Здесь таблица массивов разного размера 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 |
+--------+-----------+-----------+---------+------------+-------+
На основе чисел SZARRAY
я бы предположил, что мои поля QueuedEvent
выравниваются по границам Int32, таким образом беря байты 12. Но я не знаю, откуда взялись дополнительные 72 байта.
Изменить: Я получаю эти цифры, вызывая Debug.GC(true)
и наблюдая за дампом, который я получаю в своем отладчике. Я не нашел ссылку, которая точно определяет, что означает каждое из чисел.
Я понимаю, что могу просто выделить int[]
, но это означает, что я теряю красивую инкапсуляцию и безопасность любого типа в структуре. И я действительно хотел бы узнать, какова истинная стоимость структуры в микроструктуре.
My TinyTimeSpan
очень похож на обычный TimeSpan
, за исключением того, что используется Int16
для представления количества миллисекунд, а не Int64, представляющего 100ns тиков.
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; } }
}
Я использую FEZ Domino как аппаратное обеспечение. Вполне возможно, что это аппаратное обеспечение. Кроме того, Micro Framework 4.1.
Изменить - Больше ответов на тестирование и комментарии
Я провел целую кучу больше тестов (в эмуляторе на этот раз, а не на реальном оборудовании, но числа для QueuedEvent
совпадают, поэтому я предполагаю, что мое оборудование будет идентичным для других тестов).
репозиторий BitBucket с реплицируемым кодом.
Следующие интегральные типы и структуры не привлекают никаких накладных расходов как VALUETYPE
:
- Байт (1 байт)
- Int32 (4 байта)
- Int16 (2 байта)
- Int64 (8 байт)
- Двойной (8 байтов)
- TimeSpan (12 байтов - странно, поскольку его внутренний член является Int64)
- DateTime (12 байт - странно)
Однако Guid
делает: каждый использует 36 байт.
Пустой статический член выделяет VALUETYPE
, используя 72 байта (на 12 байт меньше той же структуры в массиве).
Выделение массива как члена static
ничего не меняет.
Работа в режимах отладки или выпуска не имеет значения. Я не знаю, как получить информацию об отладке GC без приложенного отладчика. Но Micro Framework интерпретируется, поэтому я не знаю, какой эффект будет иметь отложенный отладчик.
Micro Framework не поддерживает код unsafe
. Он также не поддерживает StructLayout
Explicit
(ну, технически это так, но нет атрибута FieldOffset
). StructLayout
Auto
и Sequential
не имеют значения.
Вот несколько структур и их выделенное распределение памяти:
// 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
{
}
Кажется, каждый раз, когда я использую настраиваемую структуру, я беру на себя какие-то накладные расходы. И независимо от того, что я включаю в структуру, она всегда требует 12 байтов в SZARRAY
. Поэтому я попробовал это:
// 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;
}
Незначительное изменение
После публикации моего собственного ответа я понял, что в элементе SZARRAY
на элемент всегда было 12 байт. Поэтому я тестировал object[]
. Типы ссылок потребляют по 12 байт в Micro Framework.
Пустая структура public struct Empty { }
потребляет 24 байта каждый.