Почему максимальный размер байта [] 2 ГБ - 57 В?
На моей 64-битной машине этот код C# работает:
new byte[2L * 1024 * 1024 * 1024 - 57]
но этот бросает OutOfMemoryException
:
new byte[2L * 1024 * 1024 * 1024 - 56]
Зачем?
Я понимаю, что максимальный размер управляемого объекта составляет 2 ГБ и что создаваемый объект массива содержит больше байтов, чем я хочу. А именно, есть 4 байта (или 8?) Для номера синхронизирующего блока, 8 байтов для ссылки на MethodTable и 4 байта для размера массива. Это 24 байта, включая заполнение, так почему я не могу выделить массив с 2G - 24 байта? Действительно ли максимальный размер действительно равен 2 ГБ? Если это так, для чего используются оставшиеся 2 ГБ?
Примечание. На самом деле мне не нужно выделять массив с 2 миллионами байтов. И даже если бы я сделал, 56 байтов незначительно накладные расходы. И я мог легко обойти предел, используя пользовательские BigArray<T>
,
3 ответа
Вам нужно 56 байтов накладных расходов. Это на самом деле 2,147,483,649-1 минус 56 для максимального размера. Вот почему ваш минус 57 работает, а минус 56 нет.
Как сказал здесь Джон Скит:
Однако на практике я не верю, что какая-либо реализация поддерживает такие огромные массивы. CLR имеет ограничение для каждого объекта чуть более 2 ГБ, поэтому даже байтовый массив не может иметь 2147483648 элементов. Немного экспериментов показывает, что на моем компьютере самый большой массив, который вы можете создать, это новый байт [2147483591]. (Это на 64-битной.NET CLR; версия Mono, на которой у меня установлены дроссели.)
Смотрите также эту статью InformIT на ту же тему. Он предоставляет следующий код для демонстрации максимальных размеров и накладных расходов:
class Program
{
static void Main(string[] args)
{
AllocateMaxSize<byte>();
AllocateMaxSize<short>();
AllocateMaxSize<int>();
AllocateMaxSize<long>();
AllocateMaxSize<object>();
}
const long twogigLimit = ((long)2 * 1024 * 1024 * 1024) - 1;
static void AllocateMaxSize<T>()
{
int twogig = (int)twogigLimit;
int num;
Type tt = typeof(T);
if (tt.IsValueType)
{
num = twogig / Marshal.SizeOf(typeof(T));
}
else
{
num = twogig / IntPtr.Size;
}
T[] buff;
bool success = false;
do
{
try
{
buff = new T[num];
success = true;
}
catch (OutOfMemoryException)
{
--num;
}
} while (!success);
Console.WriteLine("Maximum size of {0}[] is {1:N0} items.", typeof(T).ToString(), num);
}
}
Наконец, в статье есть что сказать:
Если вы посчитаете, вы увидите, что накладные расходы на выделение массива составляют 56 байтов. В конце осталось несколько байтов из-за размеров объекта. Например, 268 435 448 64-разрядных чисел занимают 2 147 483 584 байта. Добавление заголовка массива 56 байтов дает вам 2 147 483 640, оставляя вам 7 байтов меньше 2 гигабайт.
Редактировать:
Но подождите, это еще не все!
Осмотревшись вокруг и поговорив с Джоном Скитом, он указал мне на статью о памяти и строках, которую он написал. В этой статье он приводит таблицу размеров:
Type x86 size x64 size
object 12 24
object[] 16 + length * 4 32 + length * 8
int[] 12 + length * 4 28 + length * 4
byte[] 12 + length 24 + length
string 14 + length * 2 26 + length * 2
Мистер Скит продолжает:
Вам может быть прощено смотреть на числа выше и думать, что "накладные расходы" объекта составляют 12 байтов в x86 и 24 в x64... но это не совсем верно.
и это:
"Базовые" накладные расходы составляют 8 байтов на объект в x86 и 16 на объект в x64... учитывая, что мы можем хранить Int32 "реальных" данных в x86 и при этом иметь размер объекта 12, а также мы можем хранить два Int32 реальных данных в x64 и все еще имеют объект x64.
Существует "минимальный" размер 12 байтов и 24 байта соответственно. Другими словами, вы не можете иметь тип, который является просто накладными расходами. Обратите внимание, что класс "Пустой" принимает тот же размер, что и при создании экземпляров Object… фактически есть некоторая свободная комната, потому что CLR не любит работать с объектом без данных. (Обратите внимание, что структура без полей также занимает место, даже для локальных переменных.)
Объекты x86 дополняются до 4-байтовых границ; на x64 это 8 байт (как и раньше)
и наконец Джон Скит ответил на вопрос, который я ему задал, в другом вопросе, где он заявляет (в ответ на статью InformIT, которую я ему показал):
Похоже, что статья, на которую вы ссылаетесь, выводит накладные расходы только из предела, что является глупым ИМО.
Таким образом, чтобы ответить на ваш вопрос, фактические издержки составляют 24 байта с 32 байтами свободной комнаты, из того, что я собираю.
Одно можно сказать наверняка: у вас не может быть нечетного количества байтов, оно обычно кратно собственному размеру слова, который составляет 8 байтов в 64-битном процессе. Таким образом, вы можете добавить еще 7 байтов в массив.
На самом деле вы можете найти это ограничение явно установленным и проверенным в исходном коде .net, и оно дает некоторое представление о том, почему это было сделано (эффективная реализация исключения расширенной проверки диапазона в будущем и обратная совместимость в случае байтов):
inline SIZE_T MaxArrayLength(SIZE_T componentSize)
{
// Impose limits on maximum array length in each dimension to allow efficient
// implementation of advanced range check elimination in future. We have to allow
// higher limit for array of bytes (or one byte structs) for backward compatibility.
// Keep in sync with Array.MaxArrayLength in BCL.
return (componentSize == 1) ? 0X7FFFFFC7 : 0X7FEFFFFF;
}
...
SIZE_T componentSize = pArrayMT->GetComponentSize();
if ((SIZE_T)cElements > MaxArrayLength(componentSize))
ThrowOutOfMemoryDimensionsExceeded();