Как я могу узнать АКТУАЛЬНОЕ максимальное количество элементов, которое может быть выделен массив .net данного типа? - PullRequest
2 голосов
/ 03 декабря 2009

Я знаю, что все массивы в .net ограничены 2 ГБ, поэтому я стараюсь не выделять больше, чем n = ((2 ^ 31) - 1) / 8, удваивается в массиве. Тем не менее, это количество элементов все еще не является действительным. Кто-нибудь знает, как я могу определить во время выполнения максимальное количество элементов с учетом sizeof (T)?

Я знаю, что любое количество, приближающееся к этому числу, является просто множеством элементов, но, для всех намерений и целей, допустим, оно мне нужно.

Примечание. Я нахожусь в 64-разрядной среде с целевой платформой для моего приложения AnyCPU и свободным объемом ОЗУ не менее 3100 МБ.

Обновление: Спасибо всем за ваш вклад и извините, я был так тихо. Я прошу прощения за неудобства. Я не смог перефразировать свой вопрос, но могу добавить, что то, что я ищу, - это решить что-то вроде этого:

template <class T>
array<T>^ allocateAnUsableArrayWithTheMostElementsPossible(){
    return gcnew array<T>( ... );
}

Результаты в моем собственном ответе вроде удовлетворительные, но недостаточно хорошие. Кроме того, я не тестировал его на другой машине (вроде трудно найти другую машину с более чем 4 ГБ). Кроме того, я проводил некоторые исследования самостоятельно, и, кажется, нет дешевого способа вычислить это во время выполнения. В любом случае, это был только плюс, никто из пользователей что я пытаюсь достичь не может ожидать использования функции, которую я пытаюсь реализовать, не имея возможности.

Итак, другими словами, я просто хочу понять, почему максимальное количество элементов массива не может составлять до 2 ГБ при прочих равных условиях . Максимум - это все, что мне нужно на данный момент.

Ответы [ 5 ]

2 голосов
/ 03 декабря 2009

Итак, я запустил живую программу, чтобы выяснить некоторые жесткие значения, и вот что я нашел:

  • Учитывая тип T, f (sizeof (T)) = N + d

    • Где f - реальный максимальный размер массив ц.
    • Н теоретический максимальный размер, то есть: Int32 :: MaxValue / sizeof (T)
    • А d - это разница между N и f (x).

Результаты:

  • f (1) = N - 56
  • f (2) = N - 28
  • f (4) = N - 14
  • f (8) = N - 7
  • f (16) = N-3
  • f (32) = N - 1

Я вижу, что каждый раз, когда размер увеличивается, разница между реальным и теоретическим размером уменьшается, но не в степени 2. Есть идеи, почему?

Редактировать: d - количество элементов типа T. Чтобы найти d в байтах, выполните sizeof(T) * d.

2 голосов
/ 03 декабря 2009

Обновление: ответ ПОЛНОСТЬЮ переписан. Оригинальный ответ содержал методы, чтобы найти максимально возможный адресуемый массив в любой системе путем деления и завоевания, смотрите историю этого ответа, если вам интересно. Новый ответ пытается объяснить разрыв в 56 байт.

В своем собственном ответе А.З. пояснил, что максимальный размер массива ограничен менее 2 ГБ, и с некоторыми пробами и ошибками (или другим методом?) Находит следующее (краткое изложение):

  • Если размер типа составляет 1, 2, 4 или 8 байт, максимальный размер для заполнения составляет 2 ГБ - 56 байт;
  • Если размер типа составляет 16 байт, максимальный размер составляет 2 ГБ - 48 байт;
  • Если размер типа составляет 32 байта, максимальный размер составляет 2 ГБ - 32 байта.

Я не совсем уверен насчет ситуации с 16 байтами и 32 байтами. Общий доступный размер для массива может отличаться, если это массив структур или встроенный тип. Я подчеркну размер шрифта 1-8 байт (в этом я тоже не уверен, см. Заключение).

Макет данных массива

Чтобы понять, почему CLR не допускает точно 2GB / IntPtr.Size элементов, нам нужно знать, как устроен массив. Хорошей отправной точкой является эта SO статья , но, к сожалению, некоторая информация кажется ложной или, по крайней мере, неполной. Эта углубленная статья о том, как .NET CLR создает объекты среды выполнения оказалась бесценной, а также недокументированных массивов статья о CodeProject.

Взяв всю информацию из этих статей, мы получаем следующую компоновку массива в 32-битных системах:

Single dimension, built-in type
SSSSTTTTLLLL[...data...]0000
^ sync block
    ^ type handle
        ^ length array
                        ^ NULL 

Каждая часть - одна система размером DWORD. В 64-битных окнах это выглядит следующим образом:

Single dimension, built-in type
SSSSSSSSTTTTTTTTLLLLLLLL[...data...]00000000
^ sync block
        ^ type handle
                ^ length array
                                    ^ NULL 

Макет выглядит немного иначе, когда он представляет собой массив объектов (то есть строк, экземпляров классов). Как видите, добавлен дескриптор типа для объекта в массиве.

Single dimension, built-in type
SSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
^ sync block
        ^ type handle
                ^ length array
                        ^ type handle array element type
                                            ^ NULL 

Глядя дальше, мы обнаруживаем, что встроенный тип, или фактически любой тип структуры, получает собственный обработчик определенного типа (все uint имеют одинаковый общий доступ, но int имеет обработчик другого типа для массива затем uint или byte). Все массивы объектов имеют один и тот же обработчик типов, но имеют дополнительное поле, которое указывает на обработчик типов объектов.

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

Все еще не 56 байтов ...

Чтобы сосчитать 56 байтов ответа AZ, я должен сделать несколько предположений. Я предполагаю, что:

  1. дескриптор syncblock и type учитывает размер объекта;
  2. переменная, содержащая ссылку на массив (указатель объекта), учитывает размер объекта;
  3. нулевой терминатор массива учитывается в размере объекта.

Синхронный блок помещается перед адресом, на который указывает переменная, поэтому выглядит так, как будто он не является частью объекта . Но на самом деле, я верю, что это так и имеет значение для внутреннего ограничения в 2 ГБ. Добавляя все это, мы получаем, для 64-битных систем:

ObjectRef + 
Syncblock +
Typehandle +
Length +
Null pointer +
--------------
40  (5 * 8 bytes)

Не 56 пока. Возможно, кто-то может взглянуть на Memory View во время отладки, чтобы проверить, как выглядит компоновка массива под 64-битными окнами.

Полагаю, что-то вроде этого (выбирайте, смешивайте и сочетайте):

  • 2ГБ никогда не будут возможны, так как это один байт в следующем сегменте. Самый большой блок должен быть 2GB - sizeof(int). Но это глупо, так как индексы mem должны начинаться с нуля, а не с одного;
  • Любой объект, размер которого превышает 85016 байт, будет помещен в LOH (куча больших объектов). Это может включать в себя дополнительный указатель или даже 16-байтовую структуру, содержащую информацию LOH. Возможно, это относится к пределу;
  • Выравнивание: при условии, что objectref не считается (в любом случае он находится в другом сегменте mem), общий разрыв составляет 32 байта. Вполне возможно, что система предпочитает 32-байтовые границы. Взгляните по-новому на макет памяти. Если начальная точка должна находиться на границе 32 байта, и ей нужно место для синхронизирующего блока перед ней, синхронизирующий блок окажется в конце первого 32 байтового блока. Примерно так:

    XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
    

    , где XXX.. обозначает пропущенные байты.

  • многомерных массивов: если вы динамически создаете массивы с помощью Array.CreateInstance с 1 или более измерениями, будет создан один массив dim с двумя дополнительными DWORDS, содержащими размер и нижнюю границу измерения (даже если у вас есть только один размерность, но только если нижняя граница указана как ненулевая). Я считаю это крайне маловероятным, поскольку вы, вероятно, упомянули бы об этом, если бы это было так в вашем коде. Но это принесло бы общее количество до 56 байт;).

Заключение

Из всего, что я собрал во время этого небольшого исследования, я думаю, что Overhead + Aligning - Objectref является наиболее вероятным и наиболее подходящим выводом. Однако «настоящий» гуру CLR мог бы пролить некоторый дополнительный свет на этот специфический предмет.

Ни один из этих выводов не объясняет, почему 16 или 32-байтовые типы данных имеют разрыв 48 и 32 байта соответственно.

Спасибо за сложную тему, кое-что узнал на моем пути. Возможно, некоторые люди могут снять отрицательный голос, когда обнаружат, что этот новый ответ более связан с вопросом (который я изначально неправильно понял, и извинениями за беспорядок, который это могло вызвать).

0 голосов
/ 03 декабря 2009

Вам также необходимо добавить размер указателя (System.IntPtr.Size) к каждому sizeof (T), чтобы учесть указатель на объект в любом данном элементе массива.

0 голосов
/ 03 декабря 2009

Обновление: мой другой ответ содержит решение , но я оставляю это для информации о Mono, C #, ссылках CLR и ветке обсуждения

Максимальный размер массива ограничен размером целого числа, а не размером объектов, которые он содержит. Но любой объект в .NET ограничен 2 ГБ, точка (благодаря Люку и см. РЕДАКТИРОВАТЬ), который ограничивает общий размер вашего массива, который является суммой отдельных элементов плюс немного накладных расходов.

Причина, по которой она душит вашу систему, - это доступная память системы. А система процесса win32 позволяет вам использовать только 2 ГБ памяти, из которой ваша программа и CLR уже используют довольно мало даже до запуска массива. Остальное вы можете использовать для своего массива:

int alot = 640000000;
byte[] xxx = new byte[1U << 31 - alot];

Это зависит от того, как настроен ваш CLR, не хватает ли у вас памяти. Например, в ASP.NET вы по умолчанию связаны с 60% общей доступной памяти машины.

РЕДАКТИРОВАТЬ: Этот ответ на соответствующий пост идет немного глубже в тему и проблемы с 64-битной. Это возможно на 64-битных системах, но только с использованием обходных путей. Это указывает на этот превосходный пост в блоге на тему , который объясняет BigArray<T>.

ПРИМЕЧАНИЕ 1. Другие CLR, т.е. Mono, просто допускают объекты размером более 2 ГБ.

ПРИМЕЧАНИЕ 2: это не язык, который ограничивает вас. Это прекрасно компилируется в C #, но попытаться наладить машину, на которой это не происходит, - довольно футуристическая мысль (и, честно говоря, поле в классе Array, содержащее длину, равно int, что означает, что это будет всегда в 32-битном режиме, но не обязательно, хотя и весьма вероятно, в любой 64-битной реализации):

int[] xxx = new int[0xFFFFFFFFFFFFFFFF];  // 2^64-1
0 голосов
/ 03 декабря 2009

Ваше пространство процесса ограничено 2 ГБ, если вы не [скомпилированы anycpu или x64] и не работаете в процессе x64 [на компьютере x64]. Это то, с чем вы, вероятно, действительно сталкиваетесь. Вычисление запаса, имеющегося у вас в процессе, никоим образом не является точной наукой.

(угол Nitpickers: есть переключатель / 3GB и стеки других пограничных случаев, которые влияют на это. Кроме того, процессу необходимо иметь виртуальное или физическое пространство, которое также должно быть распределено. люди чаще сталкиваются с ОС на ограничение процесса, чем любое ограничение .NET)

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