Сколько байтов распределено для 3-х точечных структур на 64-битном процессоре?
Возникает вопрос:
Дано:
struct Point {int x; int y;}
var p = new Point[3]
сколько байтов памяти будет выделено в стеке и в куче, если мы будем использовать 64-разрядный процессор?
Правильный ответ для .Net
44. Может ли кто-нибудь объяснить, как появилось это число?
Насколько я понимаю, p
будет занимать 8 байт в стеке для x64
.
И у нас есть два значения Int32
для каждой структуры, таким образом
p.Length * sizeof(Point)
3 * 8 = 24 байта в куче для массива.
Это будет 32 байта. Что остальное 12 байт в этом случае?
Ответы
Ответ 1
Ваш ответ 44 байт, вероятно, является путаницей, связанной с массивом из 32-битной архитектуры.
В .Net
(32 бит):
- Каждый
object
содержит 4 байта для синхронизации (lock (obj)
).
- Каждый
object
содержит 4 байта своего токена типа.
- Каждый
array
содержит 4 байта своей длины.
Указатель 8 байт, как вы сказали.
Это с 24 байтами самого массива дает вам 44 байта.
Однако, это макет заголовка для 32 бит.
Как вы можете видеть, макет памяти следующего кода:
var p = new Point[3];
p[0] = new Point { x = 1, y = 2 };
p[1] = new Point { x = 3, y = 4 };
p[2] = new Point { x = 5, y = 6 };
var p2 = new Point[3];
p2[0] = new Point { x = 8, y = 8 };
p2[1] = new Point { x = 8, y = 8 };
p2[2] = new Point { x = 8, y = 8 };
Будет:
![Макет памяти]()
Вы также можете увидеть числовые значения в макете памяти.
В 64-битном поле каждое поле заголовка имеет 8 байт, поэтому длина заголовка 24 байта, поэтому длина всего массива 48 байт и с переменной, указывающей на массив: 56 байт.
64-битная архитектура:
![64-битная макет памяти]()
Примечания:
-
Если ваш массив не был округлен до 8-битного множественного выравнивания, он будет иметь место, но это не требует выравнивания. Пример (два массива размера int
1):
![Выравнивание]()
-
Даже если поле длины заголовка 8 байт в 64 бит, оно больше максимального размера массива .Net
, поэтому можно использовать только 4.
Имейте в виду, что это деталь реализации, и она может меняться между реализациями/версиями CLR.
Ответ 2
Большая часть этого является исключительно детальностью реализации и может измениться со следующей версией CLR.
Запустите следующую программу как x86 или x64, и вы можете эмпирически определить размер вашей структуры:
struct Point { int x; int y; }
class Program
{
const int Size = 100000;
private static void Main(string[] args)
{
object[] array = new object[Size];
long initialMemory = GC.GetTotalMemory(true);
for (int i = 0; i < Size; i++)
{
array[i] = new Point[3];
}
long finalMemory = GC.GetTotalMemory(true);
GC.KeepAlive(array);
long total = finalMemory - initialMemory;
Console.WriteLine("Size of each element: {0:0.000} bytes",
((double)total) / Size);
}
}
Код довольно прост, но бесстыдно украден у Jon Skeet.
Если вы запустите это, вы получите следующие результаты:
x86: 36 byte
x64: 48 byte
В текущей реализации размер каждого объекта выравнивается с размером указателя, это означает, что каждый объект в x86 равен 4 байтам, а под x64 8 байтами (это абсолютно возможно изменить - HotSpot в Java, например, выравнивает все до 8 байтов даже под x86).
Массивы в С# несколько отличаются своей длиной: в то время как у них есть поле длиной 4 байта, под x64 они также включают в себя 4 байта дополнительного дополнения (vm/object.h: 766 содержит интересную часть). Это, скорее всего, сделано, чтобы гарантировать, что начало фактических полей всегда равно 8 байтам, выровненным под x64, что необходимо для получения хорошей производительности при доступе к longs/doubles/pointers (альтернативой было бы только добавление дополнения для этих типов и специализация вычисление длины - вряд ли будет стоить дополнительной сложности).
В x86 заголовок объекта имеет 8 байт и служебный 4 байта массива, что дает нам 36 байт.
В x64 заголовок объекта составляет 16 байт, а служебный 8-байтовый массив. Это дает нам 24 + 24 = 48 байт.
Для тех, кто хочет фактическое доказательство вместо эмпирических тестов о размере и выравнивании заголовка, вы можете просто перейти к фактическому источнику: Здесь - объект определение coreclr. Отметьте комментарий, начинающийся со строки 178, который гласит:
// The only fields mandated by all objects are
//
// * a pointer to the code:MethodTable at offset 0
// * a poiner to a code:ObjHeader at a negative offset. This is often zero. It holds information that
// any addition information that we might need to attach to arbitrary objects.
Вы также можете посмотреть фактический код, чтобы увидеть, что эти указатели являются фактическими указателями, а не DWORD или чем-либо еще.
Код для выравнивания размеров объекта также находится в одном файле:
#define PTRALIGNCONST (DATA_ALIGNMENT-1)
#ifndef PtrAlign
#define PtrAlign(size) \
((size + PTRALIGNCONST) & (~PTRALIGNCONST))
#endif //!PtrAlign
DATA_ALIGNMENT
определяется как 4 для x86 (vm/i386/cgencpu.h) и ARM (vm/arm/cgencpu.h) и 8 для x64 (vm/amd64/cgencpu.h). Сам код является ничем иным, как стандартным оптимизированным "от кругового к следующему краю DATA_ALIGNMENT
", при условии, что выравнивание данных является степенью 2-х методов.
Ответ 3
Говоря о архитектуре x86
, ответ 44 байт неверен, поскольку размер ссылки на объект в x86
составляет 4 байта, а не 8 байтов, поэтому длина объекта 36 байт + 4 байта от ссылки на объект дает 40 байт. Исправьте меня, если я ошибаюсь.