Выравнивание вдоль 4-байтовых границ

Помните региональные различия (Евро используют периоды и запятые обратным способом как американцы), плюс знак "минус" (или соглашение обертывания числа в круглых скобках для указания отрицательный), плюс экспоненциальное представление (я достигаю на тот одном).

11
задан Tony the Pony 6 August 2009 в 09:51
поделиться

9 ответов

Это определенно может вызвать проблемы в некоторых системах.

Например, в системах на базе ARM вы не можете адресовать 32-битное слово, которое не выровнено до 4-байтовой границы. Это приведет к исключению нарушения прав доступа. На x86 вы можете получить доступ к таким невыровненным данным, хотя производительность немного страдает, так как из памяти нужно извлекать два слова вместо одного.

20
ответ дан 3 December 2019 в 00:59
поделиться

Вот что Intel x86 / x64 Reference Manual говорит о выравнивании:

4.1.1 Выравнивание слов, двойных слов, четверных слов и двойных четверных слов

Слова, двойные слова , а четверные слова делают не нужно выравнивать в памяти на естественные границы. Естественный границы для слов, двойных слов, и четверные слова четные адреса, адреса делятся поровну на четыре, и адреса равномерно делится на восемь соответственно. Однако для повышения производительности программы, структуры данных (особенно стопки) должны быть выровнены по натуральному границы по возможности. В Причина в том, что процессор требует двух обращений к памяти, чтобы сделать невыровненный доступ к памяти; выровнен для доступа требуется только одна память доступ. Слово или двойное слово операнд который пересекает 4-байтовую границу или операнд четверного слова, который пересекает Считается 8-байтовая граница невыровнен и требует двух отдельных шина памяти циклов для доступа.

Некоторые инструкции, которые работают на двойные четверные слова требуют памяти операнды должны быть выровнены по естественному граница. Эти инструкции генерируют исключение общей защиты (#GP) если указан невыровненный операнд. Урочище для двоих четверное слово - любой адрес равномерно делится на 16. Прочие инструкции которые работают с двойными четверными словами разрешить невыровненный доступ (без создание общей защиты исключение). Однако дополнительная память автобусные циклы необходимы для доступа невыровненные данные из памяти.

Не забывайте, справочные руководства являются основным источником информации для ответственных разработчиков и инженеров, поэтому, если вы имеете дело с чем-то хорошо документированным, например, с процессорами Intel, просто посмотрите, что это справочное руководство говорит о проблеме.

13
ответ дан 3 December 2019 в 00:59
поделиться

Да, это может вызвать ряд проблем. Стандарт C ++ на самом деле не гарантирует, что он будет работать. Вы не можете просто произвольно преобразовывать типы указателей.

Когда вы приводите указатель типа char к двойному указателю, он использует reinterpret_cast , который применяет отображение , определенное реализацией. Вам не гарантируется, что результирующий указатель будет содержать тот же битовый шаблон или что он будет указывать на тот же адрес или, ну, что-то еще. Говоря более практическим языком, вам также не гарантируется, что значение, которое вы читаете, выровнено правильно. Если данные были записаны как серия символов, они будут использовать требования выравнивания char.

Что касается выравнивания, по сути, просто начальный адрес значения должен делиться на размер выравнивания. Адрес 16 выравнивается по 1, 2, 4, 8 и 16-байтовым границам, например, поэтому на типичных ЦП значения этих размеров могут храниться там.

Адрес 6 не выравнивается по 4-байтовой границе. , поэтому мы не должны хранить там 4-байтовые значения.

Стоит отметить, что даже на процессорах, которые не принудительно или не требуют выравнивания, вы обычно все равно получаете значительное замедление от доступа к невыровненным значениям.

4
ответ дан 3 December 2019 в 00:59
поделиться

Выравнивание влияет на расположение структур. Рассмотрим эту структуру:

struct S {
  char a;
  long b;
};

На 32-битном процессоре структура этой структуры часто будет такой:

a _ _ _ b b b b

Требование состоит в том, что 32-битное значение должно быть выровнено по 32-битной границе. Если структура изменяется следующим образом:

struct S {
  char a;
  short b;
  long c;
};

макет будет следующим:

a _ b b c c c c

16-битное значение выровнено по 16-битной границе.

Иногда вы хотите упаковать структуры возможно, если вы хотите сопоставить структуру с форматом данных. Используя параметр компилятора или, возможно, #pragma , вы можете удалить лишнее пространство:

a b b b b
a b b c c c c

Однако доступ к невыровненному члену упакованной структуры часто будет намного медленнее на современных процессорах, а может даже приведет к исключению.

4
ответ дан 3 December 2019 в 00:59
поделиться

На x86 он всегда будет работать, конечно, более эффективно при выравнивании.

Но если вы МНОГОПРОЧИТЕЛЬНЫЙ, то следите за чтением-записью-разрывом. При 64-битном значении вам понадобится машина x64, чтобы обеспечить атомарное чтение и запись между потоками.
Если, скажем, вы читаете значение из другого потока, когда он говорит, что он увеличивается между 0x00000000.FFFFFFFF и 0x00000001.00000000, то другой поток теоретически может читать, скажем, 0 или 1FFFFFFFF, особенно ЕСЛИ СКАЗЫВАЕТ значение STRADDLED A CACHE-LINE.
Я рекомендую Даффи «Параллельное программирование в Windows» за хорошее обсуждение моделей памяти, даже упоминание ошибок выравнивания на мультипроцессорах, когда dot-net выполняет сборку мусора. Вы хотите держаться подальше от Itanium!

2
ответ дан 3 December 2019 в 00:59
поделиться

Принудительное выравнивание памяти гораздо чаще встречается в архитектурах на основе RISC , таких как MIPS.
Основное мышление для этих типов процессоров, AFAIK, действительно связано с проблемой скорости.
Методология RISC заключалась в наличии набора простых и быстрых инструкций (обычно один цикл памяти на инструкцию). Это не обязательно означает, что у него меньше инструкций, чем у процессора CISC, больше, что он имеет более простые и быстрые инструкции.
Многие процессоры MIPS, хотя с адресацией 8 байт, будут выровнены по словам (обычно 32 бита, но не всегда), а затем маскируют соответствующие биты.
Идея в том, что это быстрее выполнить выровненную нагрузку + битовую маску, чем пытаться выполнить невыровненную загрузку. Как правило (и, конечно, это действительно зависит от набора микросхем), выполнение невыровненной загрузки приведет к ошибке шины, поэтому процессоры RISC будут предлагать команду «невыровненная загрузка / сохранение», но это часто будет намного медленнее, чем соответствующая выровненная загрузка / сохранение. .

Конечно, это все еще не отвечает на вопрос, почему они это делают, т.е. какое преимущество дает вам выравнивание слов в памяти? Я не специалист по аппаратному обеспечению, и я уверен, что кто-нибудь из присутствующих может дать лучший ответ, но мои два лучших предположения:
1. При выравнивании слов извлечение из кеша может быть намного быстрее, потому что многие кеши организованы в строки кэша (от 8 до 512 байт), а поскольку кэш-память обычно намного дороже ОЗУ, вы хотите максимально использовать из него.
2. Доступ к каждому адресу памяти может быть намного быстрее, так как он позволяет вам читать в «пакетном режиме» (т. Е. Извлекать следующий последовательный адрес до того, как он понадобится)

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

1
ответ дан 3 December 2019 в 00:59
поделиться

Да, это может вызвать проблемы.

Выравнивание по 4 просто означает, что указатель, рассматриваемый как числовой адрес, кратен 4. Если указатель не кратен требуемое выравнивание, то оно не выравнивается. Есть две причины, по которым компиляторы накладывают ограничения на выравнивание для определенных типов:

  1. Потому что оборудование не может загрузить этот тип данных из невыровненного указателя (по крайней мере, не используя инструкции, которые компилятор хочет выдать для загрузки и сохранения).
  2. Поскольку оборудование загружает этот тип данных быстрее из выровненных указателей.

Если у вас case (1), а double является 4-выровненным, и вы пробуете свой код с указателем char * , который является а не с четырьмя выравниванием, то вы, скорее всего, получите аппаратную ловушку. Некоторое оборудование не ловит. Он просто загружает бессмысленное значение и продолжает работу. Однако, стандарт C ++ не определяет, что может случиться (неопределенное поведение), поэтому этот код может вызвать возгорание вашего компьютера.

На x86 вы никогда не в случае (1), потому что стандартные инструкции загрузки могут обрабатывать невыровненные указатели . В ARM нет невыровненных загрузок, и если вы попытаетесь это сделать, ваша программа выйдет из строя (если вам повезет. Некоторые ARM молча терпят неудачу).

Возвращаясь к вашему примеру, вопрос в том, почему вы пытаетесь это сделать с char * , не выровненный по 4 линиям. Если вы успешно записали туда двойное число через двойное * , то вы сможете прочитать его обратно. Итак, если у вас изначально был «правильный» указатель на double, который вы приводили к char * , а теперь отбрасываете обратно, вам не нужно беспокоиться о выравнивании.

Но вы сказали произвольно. символ * , так что я думаю, это не то, что у вас есть. Если вы читаете фрагмент данных из файла, который содержит сериализованное двойное значение, тогда вы должны убедиться, что требования выравнивания для вашей платформы выполнены, чтобы выполнить это приведение. Если у вас есть 8 байтов, представляющих double в каком-либо формате файла, вы не можете просто прочитать его волей-неволей в буфер char * с любым смещением, а затем преобразовать в double * .

Самый простой способ сделать это, чтобы убедиться, что вы считываете данные файла в подходящую структуру. Вам также помогает тот факт, что выделения памяти всегда выравниваются по максимальному требованию выравнивания любого типа, который они достаточно велики, чтобы вместить. Поэтому, если вы выделяете буфер, достаточно большой, чтобы содержать double, то начало этого буфера имеет выравнивание, необходимое для double. Итак, вы можете прочитать 8 байтов, представляющих двойное значение, в начало буфера, преобразовать (или использовать объединение) и прочитать двойное значение.

В качестве альтернативы вы могли бы сделать что-то вроде этого:

double readUnalignedDouble(char *un_ptr) {
    double d;
    // either of these
    std::memcpy(&d, un_ptr, sizeof(d));
    std::copy(un_ptr, un_ptr + sizeof(d), reinterpret_cast<char *>(&d));
    return d;
}

Это гарантированно быть действительным (при условии, что un_ptr действительно указывает на байты действительного представления double для вашей платформы), потому что double - это POD и, следовательно, может быть скопирован побайтно. Возможно, это не самое быстрое решение, если у вас есть много двойников для загрузки.

Если вы читаете из файла, на самом деле это немного больше, чем это, если вы беспокоитесь о платформах с двойными копиями, отличными от IEEE. представления, или с 9-битными байтами, или с некоторыми другими необычными свойствами, где могут быть биты, не являющиеся значениями в сохраненном представлении двойника. Но вы на самом деле не спрашивали о файлах, я просто придумал это в качестве примера, и в любом случае эти платформы встречаются гораздо реже, чем проблема, о которой вы спрашиваете, которая заключается в том, что для double требуется выравнивание.

Наконец, ничего общего с выравниванием, у вас также есть строгий псевдоним, о котором нужно беспокоиться, если вы получили этот char * посредством преобразования из указателя, который несовместим с псевдонимом double * . Однако псевдонимы допустимы между самим char * и чем-либо еще.

3
ответ дан 3 December 2019 в 00:59
поделиться

Примером требования к согласованию является использование инструкций векторизации (SIMD). (Его можно использовать без согласования, но он работает намного быстрее, если вы используете инструкцию, требующую выравнивания).

1
ответ дан 3 December 2019 в 00:59
поделиться

SPARC (машины Solaris) - это еще одна архитектура (по крайней мере, некогда в прошлом), которая подавится (выдаст ошибку SIGBUS), если вы попытаетесь использовать невыровненное значение.

Дополнение к Мартину Йорку, malloc также привязан к максимально возможному типу, т.е. он безопасен для всего, как «новый». Фактически, часто new просто использует malloc.

2
ответ дан 3 December 2019 в 00:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: