Выравнивание по 4-байтовым границам - PullRequest
11 голосов
/ 06 августа 2009

Недавно я подумал о выравнивании ... Это то, что мы обычно не должны учитывать, но я понял, что некоторые процессоры требуют, чтобы объекты выравнивались по 4-байтовым границам. Что именно это означает, и какие конкретные системы имеют требования к выравниванию?

Предположим, у меня есть произвольный указатель:

unsigned char* ptr

Теперь я пытаюсь получить двойное значение из ячейки памяти:

double d = **((double*)ptr);

Это вызовет проблемы?

Ответы [ 9 ]

20 голосов
/ 06 августа 2009

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

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

13 голосов
/ 06 августа 2009

Вот что говорит Справочное руководство Intel x86 / x64 о выравниваниях:

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

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

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

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

4 голосов
/ 06 августа 2009

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

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

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

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

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

4 голосов
/ 06 августа 2009

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

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

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

3 голосов
/ 06 августа 2009

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

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

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

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

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

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

Но вы сказали произвольно char *, так что я думаю, что это не то, что у вас есть. Если вы читаете порцию данных из файла, который содержит сериализованное двойное число, то вы должны убедиться, что требования к выравниванию для вашей платформы выполнены для выполнения этого приведения. Если у вас есть 8 байтов, представляющих двойное число в каком-либо формате файла, то вы не можете просто прочитать его произвольно в буфер 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 - это POD и, следовательно, может быть скопировано побайтово. Возможно, это не самое быстрое решение, если вам нужно загрузить много пар.

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

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

2 голосов
/ 08 августа 2009

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

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

2 голосов
/ 06 августа 2009

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

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

1 голос
/ 06 августа 2009

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

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

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

1 голос
/ 06 августа 2009

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

...