Подтвердить что ты не робот

Почему мой массив структур занимает столько памяти?

Вопрос: Как 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 байта каждый.

4b9b3361

Ответ 1

Основываясь на моих тестах, я предполагаю, что ValueTypes в Micro Framework не являются истинными типами значений, так как мы привыкли к CLR на рабочем столе. По крайней мере, они в коробке. И может быть и другой уровень косвенности. Эти затраты возникают в связи с непредвиденными издержками памяти (довольно существенные для встроенной платформы).

Я буду преобразовывать в int[] в мой FixedSizedQueue.

Фактически, я закончил использование UInt32[] и добавил некоторые методы расширения для обертывания бит-бит.

Я немного исказился в исходном коде, но не смог найти ничего полезного (и я действительно не знаю, что искать).