Расположение памяти в массиве .NET

#c# #arrays #caching #optimization

#.net #массивы #накладные расходы

Вопрос:

Какова структура памяти массива .NET?

Возьмем, к примеру, этот массив:

 Int32[] x = new Int32[10];
  

Я понимаю, что основная часть массива выглядит следующим образом:

 0000111122223333444455556666777788889999
  

Где каждый символ равен одному байту, а цифры соответствуют индексам в массиве.

Кроме того, я знаю, что для всех объектов существует ссылка на тип и индекс syncblock, поэтому вышеуказанное можно скорректировать с учетом этого:

 ttttssss0000111122223333444455556666777788889999
        ^
         - object reference points here
  

Кроме того, необходимо сохранить длину массива, так что, возможно, это более правильно:

 ttttssssllll0000111122223333444455556666777788889999
        ^
         - object reference points here
  

Это завершено? Есть ли еще данные в массиве?

Причина, по которой я спрашиваю, заключается в том, что мы пытаемся оценить, сколько памяти займет пара разных представлений в памяти довольно большого массива данных, а размер массивов варьируется совсем немного, поэтому накладные расходы могут оказать большое влияние в одном решении, но, возможно, не так сильно в другом.

Итак, в принципе, для массива, сколько там накладных расходов, это в основном мой вопрос.

И до того, как команда «массивы плохие» проснется, эта часть решения представляет собой статическую сборку с использованием одной ссылки, поэтому в использовании расширяемых списков здесь нет необходимости.

Комментарии:

1. Просто небольшое замечание: syncblock находится перед ссылкой methodtype. Переменная содержит указатель (ссылку) на ссылку methodtype ( tttt выше), пропуская часть syncblock. Макет на самом деле выглядит как ssssttttllll000011...9999NULL для массивов без определенных размеров или определенных нижних границ.

Ответ №1:

Один из способов проверить это — посмотреть на код в WinDbg. Итак, учитывая приведенный ниже код, давайте посмотрим, как это отображается в куче.

 var numbers = new Int32[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  

Первое, что нужно сделать, это найти экземпляр. Поскольку я сделал это локальным в Main() , легко найти адрес экземпляра.

По адресу мы можем создать дамп фактического экземпляра, который дает нам:

 0:000> !do 0x0141ffc0
Name: System.Int32[]
MethodTable: 01309584
EEClass: 01309510
Size: 52(0x34) bytes
Array: Rank 1, Number of elements 10, Type Int32
Element Type: System.Int32
Fields:
None
  

Это говорит нам о том, что это наш массив Int32 с 10 элементами и общим размером 52 байта.

Давайте создадим дамп памяти, в которой расположен экземпляр.

 0:000> d 0x0141ffc0
0141ffc0 [84 95 30 01 0a 00 00 00-00 00 00 00 01 00 00 00  ..0.............
0141ffd0  02 00 00 00 03 00 00 00-04 00 00 00 05 00 00 00  ................
0141ffe0  06 00 00 00 07 00 00 00-08 00 00 00 09 00 00 00  ................
0141fff0  00 00 00 00]a0 20 40 03-00 00 00 00 00 00 00 00  ..... @.........
01420000  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
01420010  10 6d 99 00 00 00 00 00-00 00 01 40 50 f7 3d 03  .m.........@P.=.
01420020  03 00 00 00 08 00 00 00-00 01 00 00 00 00 00 00  ................
01420030  1c 24 40 03 00 00 00 00-00 00 00 00 00 00 00 00  .$@.............
  

Я вставил скобки для 52 байт.

  • Первые четыре байта являются ссылкой на таблицу методов в 01309584.
  • Затем четыре байта для длины массива.
  • Далее следуют числа от 0 до 9 (каждые четыре байта).
  • Последние четыре байта равны нулю. Я не совсем уверен, но предполагаю, что именно там должна храниться ссылка на массив syncblock, если экземпляр используется для блокировки.

Редактировать: Забыл длину в первой публикации.

Список немного некорректен, потому что, как указывает ромкинс, экземпляр фактически начинается с адреса — 4, а первым полем является Syncblock.

Комментарии:

1. Последние четыре байта фактически находятся за пределами этого массива. Это потому, что полученный вами указатель смещен на объект на 4; индекс syncblock идет первым со смещением -4. ссылка

2. «Первые четыре байта являются ссылкой на таблицу методов в 01309510». — не должно быть «в 01309584»?

Ответ №2:

Отличный вопрос. Я нашел эту статью, которая содержит блок-схемы как для типов значений, так и для ссылочных типов. Также смотрите эту статью, в которой Ритчер заявляет:

[snip] с каждым массивом связана некоторая дополнительная служебная информация. Эта информация содержит ранг массива (количество измерений), нижние границы для каждого измерения массива (почти всегда 0) и длину каждого измерения. Служебные данные также содержат тип каждого элемента в массиве.

Комментарии:

1. Могу ли я предложить добавить статью Code Project «Недокументированные массивы» к этому решению: codeproject.com/KB/dotnet/arrays.aspx

2. Книга, из которой взят этот фрагмент, называется «CLR через C #», и это фантастическая книга.

3. Не могу не согласиться со Снорфусом — превосходная книга.

Ответ №3:

Отличный вопрос! Я хотел увидеть это сам, и это показалось хорошей возможностью попробовать CorDbg.exe …

Кажется, что для простых целочисленных массивов формат:

 ssssllll000011112222....nnnn0000
  

где s — блок синхронизации, l — длина массива, а затем отдельных элементов. Кажется, что в конце, наконец, 0, я не уверен, почему это так.

Для многомерных массивов:

 ssssttttl1l1l2l2????????
    000011112222....nnnn000011112222....nnnn....000011112222....nnnn0000
  

где s — блок синхронизации, t — общее количество элементов, l1 — длина первого измерения, l2 — длина второго измерения, затем два нуля?, за которыми последовательно следуют все элементы и, наконец, снова ноль.

Массивы объектов обрабатываются как массив целых чисел, содержимое на этот раз является ссылками. Неровные массивы — это массивы объектов, ссылки на которые указывают на другие массивы.

Ответ №4:

Объект array должен был бы хранить, сколько измерений он имеет, и длину каждого измерения. Таким образом, в вашу модель можно добавить по крайней мере еще один элемент данных