Выравнивание массивов в .NET

Выровнены ли массивы в .NET по какой-либо границе?

Если да, то к какому? И одинаково ли это для всех типов массивов?


person JGrand    schedule 16.03.2012    source источник
comment
Зачем вам это знать? Это деталь реализации.   -  person Oded    schedule 16.03.2012
comment
Конечно, это деталь. Важный. Спасибо.   -  person JGrand    schedule 16.03.2012
comment
Добавлю, что это лишь важная деталь в редких случаях, но не менее интересная.   -  person user7116    schedule 16.03.2012
comment
@JGrand, в некоторых случаях это может быть важно. Почему это важно для вас?   -  person svick    schedule 16.03.2012
comment
@svick Потому что я хочу создать алгоритм с поддержкой кеша.   -  person JGrand    schedule 16.03.2012
comment
См. здесь: stackoverflow.com/a/15502539/1215993   -  person Haymo Kutschbach    schedule 22.07.2017


Ответы (4)


Общеязыковая инфраструктура (ECMA-335) накладывает следующие ограничения на выравнивание:

12.6.2 Выравнивание

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

  • 1-байтовые, 2-байтовые и 4-байтовые данные правильно выравниваются, когда они хранятся на границе 1, 2 или 4 байта соответственно.
  • 8-байтовые данные правильно выравниваются, когда они хранятся на той же границе, которая требуется базовому оборудованию для атомарного доступа к собственному int.

Таким образом, int16 и unsigned int16 начинаются с четного адреса; int32, unsigned int32 и float32 начинаются с адреса, кратного 4; и int64, unsigned int64 и float64 начинаются с адреса, кратного 4 или 8, в зависимости от целевой архитектуры. Собственные типы размеров (собственный int, собственный unsigned int и &) всегда имеют естественное выравнивание (4 байта или 8 байтов, в зависимости от архитектуры). При внешней генерации они также должны быть выровнены по своему естественному размеру, хотя переносимый код может использовать выравнивание по 8 байтам, чтобы гарантировать независимость от архитектуры. Настоятельно рекомендуется, чтобы float64 был выровнен по 8-байтовой границе, даже если размер собственного int составляет 32 бита.

CLI также указывает, что вы можете использовать префикс unaligned для произвольного выравнивания. Кроме того, JIT должен создавать правильный код для чтения и записи независимо от фактического выравнивания.

Кроме того, интерфейс командной строки допускает явное расположение полей класса:

  • explicitlayout: класс, отмеченный explicitlayout, заставляет загрузчик игнорировать последовательность полей и использовать предоставленные явные правила макета в виде смещений полей и/или общего размера или выравнивания класса. Существуют ограничения на допустимые макеты, указанные в Разделе II.

...

При желании разработчик может указать размер упаковки для класса. Это информация о макете, которая используется нечасто, но позволяет разработчику контролировать выравнивание полей. Это не спецификация мировоззрения как таковая, а скорее служит модификатором, который устанавливает потолок для всех мировоззрений. Типичными значениями являются 1, 2, 4, 8 или 16. Универсальные типы не должны быть помечены explicitlayout.

person user7116    schedule 16.03.2012
comment
+1. Я предполагаю, что это либо усложняет уплотнение сборщиком мусора, либо делает его потенциально менее эффективным, верно? - person JGrand; 16.03.2012
comment
@JGrand: как это усложняет? - person user7116; 16.03.2012
comment
Поскольку массивы не могут свободно перемещаться, чтобы закрыть все (маленькие) дыры (?) - person JGrand; 16.03.2012
comment
@JGrand: возможно, по мере роста фреймворков GC среды выполнения улучшился. Скорее всего, он использует стратегию выделения фиксированного размера, что означает, что эти explicitlayout классы будут потреблять больше памяти, чем у них есть на самом деле, что упрощает сборку мусора. Теоретически это может вызвать проблемы с производительностью. - person user7116; 16.03.2012

Я не делал этого сам, но если вам нужно контролировать выравнивание массива для взаимодействия с неуправляемым режимом, вы можете рассмотреть возможность использования (небезопасного) фиксированного массива внутри структуры с примененным StructLayoutAttribute и посмотреть, если это работает.

person Jeffrey L Whitledge    schedule 16.03.2012
comment
Спасибо. Я не уверен, компилятор разрешит тип указателя как член структуры? - person JGrand; 16.03.2012
comment
@JGrand - фиксированный массив может быть членом структуры, но его длина должна быть константой времени компиляции, а тип элемента должен быть логическим, байтовым, char, коротким, int, длинным, sbyte, ushort, uint, ulong, float или double. - person Jeffrey L Whitledge; 16.03.2012

В .NET объекты (типом которых являются массивы) всегда выравниваются на основе размера указателя (например, выравнивание по 4 или 8 байтам). Таким образом, указатели объектов и массивы объектов всегда выравниваются в .NET.

Код в ответе Майкла Грачика проверяет выравнивание по индексу, потому что, хотя сам массив выровнен, поскольку это массив Int32, отдельные нечетные индексы не будут выровнены в 64-битных системах. В 32-битных системах все индексы массива Int32 будут выровнены.

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

Я также должен отметить, что разыменование указателя в .NET не требует выравнивания. Однако это будет медленнее. например если у вас есть действительный указатель byte* и он указывает на данные длиной не менее восьми байтов, вы можете привести его к long* и получить значение:

unsafe
{
    var data = new byte[ 16 ];
    fixed ( byte* dataP = data )
    {
        var misalignedlongP = ( long* ) ( dataP + 3 );
        long value = *misalignedlongP;
    }
}

Читая исходный код .NET, вы можете видеть, что Microsoft иногда учитывает выравнивание, а часто нет. Примером может служить внутренний метод System.Buffer.Memmove (см. https://referencesource.microsoft.com/#mscorlib/system/buffer.cs,c2ca91c0d34a8f86). Этот метод имеет пути кода, которые приводят byte* к типу long без каких-либо проверок выравнивания в нескольких местах, и вызывающие методы также не проверяют выравнивание.

person Mark Smeltzer    schedule 06.11.2017
comment
Позвольте мне обратить особое внимание на последний абзац, хотя в последних версиях .NET соответствующий код был недавно значительно переработан, см. diff-f3e92259d4c0662a268d2c4b6b3c13c5" rel="nofollow noreferrer">github.com/dotnet/coreclr/commit/. - person Glenn Slayden; 25.04.2018

Я ничего не знаю об управляемых массивах, но в нескольких местах код Microsoft BCL предполагает, что fixed массивы выровнены по словам. Вот пример из BitConverter.cs в .NET 4.0:

    public static unsafe int ToInt32 (byte[]value, int startIndex) { 
        //... Parameter validation

        fixed( byte * pbyte = &value[startIndex]) {
            if( startIndex % 4 == 0) { // data is aligned
                return *((int *) pbyte); 
            }
            else { 
               // .. do it the slow way
            } 
        }
    } 

Как видите, код проверяет выравнивание с помощью startIndex, а не *pbyte. Есть только две причины, почему это так:

  1. pbyte всегда выравнивается по слову.
  2. Это ошибка.

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

Я думаю, можно с уверенностью предположить, что массивы fixed всегда выравниваются по словам.

person Michael Graczyk    schedule 18.07.2012
comment
Я добавлю, что полагаюсь на это предположение в нескольких (хорошо задокументированных) местах, и это не вызвало никаких проблем. - person Michael Graczyk; 18.07.2012