Почему максимальный размер байта [] 2 ГБ - 57 B? - PullRequest
27 голосов
/ 08 июля 2011

На моей 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>.

Ответы [ 2 ]

19 голосов
/ 08 июля 2011

Вам нужно 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 байта соответственно.Другими словами, вы не можете иметь тип, который является просто накладными расходами.Обратите внимание, что класс «Empty» принимает тот же размер, что и при создании экземпляров Object ... фактически есть некоторая свободная комната, потому что CLR не любит работать с объектом без данных.(Обратите внимание, что структура без полей также занимает место, даже для локальных переменных.)

  • Объекты x86 дополняются до 4-байтовых границ;на x64 это 8 байтов (как и прежде)

и, наконец, Джон Скит ответил на вопрос, который я ему задал в другом вопросе, где он заявляет (в ответ на статью InformIT , которую я ему показал):

Похоже, статья, на которую вы ссылаетесь, выводит накладные расходы только из предела, равного глупо IMO.

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

3 голосов
/ 08 июля 2011

Одна вещь наверняка состоит в том, что вы не можете иметь нечетное количество байтов, оно обычно кратно собственному размеру слова, который составляет 8 байтов в 64-битном процессе.Таким образом, вы можете добавить еще 7 байтов в массив.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...