Вопрос

В чем разница между выровненным и невыровненным доступом к памяти?

Я работаю над DSP TMS320C64x и хочу использовать встроенные функции (функции C для инструкций по сборке), и у него есть

ushort & _amem2(void *ptr);
ushort & _mem2(void *ptr);

где _amem2 выполняет выровненный доступ к 2 байтам и _mem2 делает невыровненный доступ.

Когда мне следует использовать какой?

Это было полезно?

Решение

Выровненный доступ к памяти означает, что указатель (как целое число) кратен значению конкретного типа, называемому выравниванием.Выравнивание — это естественное число адресов, кратное тому, где тип должен находиться или должен храниться (например,по соображениям производительности) на ЦП.Например, ЦП может потребовать, чтобы все двухбайтовые загрузки или сохранения выполнялись по адресам, кратным двум.Для небольших примитивных типов (менее 4 байтов) выравнивание почти всегда соответствует размеру типа.Для структур выравнивание обычно является максимальным выравниванием любого члена.

Компилятор C всегда помещает объявленные вами переменные по адресам, которые удовлетворяют «правильному» выравниванию.Итак, если ptr указывает, например, на.переменную uint16_t, она будет выровнена, и вы сможете использовать _amem2.Вам нужно использовать _mem2 только в том случае, если вы получаете доступ, например.упакованный массив байтов, полученный через ввод-вывод, или байты в середине строки.

Другие советы

Многие компьютерные архитектуры хранят память в «словах» по несколько байтов каждое.Например, 32-битная архитектура Intel хранит 32-битные слова по 4 байта каждое.Однако память адресуется на уровне одного байта;поэтому адрес может быть «выровненным», что означает, что он начинается на границе слова, или «невыровненным», что означает, что это не так.

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

Итак, если вы знаете, что ваши адреса выровнены по правильным адресам, вы можете использовать _amem2() для скорости.В противном случае вам следует использовать _mem2().

Выровненные адреса — это адреса, кратные рассматриваемому размеру доступа.

  • Доступ к 4-байтовым словам по адресам, кратным 4, будет выровнен.
  • Доступ к 4 байтам с адреса (скажем) 3 будет невыровненным доступом

Весьма вероятно, что _mem2 Функция, которая будет работать также для невыровненного доступа, будет менее оптимальной для обеспечения правильного выравнивания в ее коде.Это означает, что _mem2 функция, вероятно, будет дороже, чем ее _amem2 версия.

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

Когда дело доходит до доступа к 2 байтам, идентифицировать выровненные операции очень просто.
Если все адреса доступа для операции «четные» (т. е. их младший бит равен нулю), у вас есть 2-байтовое выравнивание.Это легко проверить,

if (address & 1) // is true
    /* we have an odd address; not aligned */
else
    /* we have an even address; its aligned to 2-bytes */

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

Будь то драм, срам, флэш или что-то еще.Возьмем в качестве простого примера статический ОЗУ, он построен из битов. Конкретный ОЗУ будет состоять из фиксированного количества битов шириной и фиксированного количества строк в глубину.скажем, 32 бита в ширину и несколько/много строк в глубину.

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

если я выполняю 32-битную запись по адресу 0x0001 в этом ОЗУ, предполагая, что это разрешено, контроллеру нужно будет прочитать строку 0, изменить три байта, сохранив один, и записать это в строку 0, а затем прочитать строку 1 изменить один байт, оставив три других как найденные, и записать их обратно.какие байты изменяются или не имеют отношения к порядку байтов для системы.

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

Если бы мне нужно было прочитать 32 бита из адреса 0x0000, то одно чтение строки 0 было бы выполнено.Но чтение из 0x0001, и мне нужно выполнить два чтения row0 и row1, и в зависимости от конструкции системы просто отправить эти 64 бита обратно в процессор, возможно, два тактовых сигнала шины вместо одного.или контроллер памяти имеет дополнительную логику, так что 32 бита выравниваются по шине данных за один цикл шины.

16-битное чтение немного лучше: чтение из 0x0000, 0x0001 и 0x0002 будет только чтением из строки 0 и, в зависимости от конструкции системы/процессора, может отправить эти 32 бита обратно, а процессор извлекает их или сдвигает в контроллере памяти, поэтому что они попадают на определенные байтовые дорожки, поэтому процессору не приходится вращаться.Должен быть один или другой, если не оба.Чтение из 0x0003 аналогично описанному выше: вам нужно прочитать строку 0 и строку 1, поскольку в каждой из них находится один из ваших байтов, а затем либо отправить 64 бита обратно для извлечения процессора, либо контроллер памяти объединяет биты в один 32-битный ответ шины ( при условии, что в этих примерах ширина шины между процессором и контроллером памяти составляет 32 бита).

Однако 16-битная запись всегда заканчивается как минимум одним чтением-изменением-записью в этом примере статического ОЗУ, адреса 0x0000, 0x0001 и 0x0002, чтение строки 0, изменение двух байтов и обратная запись.адрес 0x0003 читает две строки, изменяет по одному байту каждую и записывает обратно.

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

Armv4 не понравился без выравнивания, хотя вы могли бы отключить ловушку, и результат не будет таким, как вы ожидали выше, не важно, текущие рычаги допускают невыравнивание и дают вам вышеуказанное поведение, вы можете немного изменить в регистре управления, и тогда он прервется без выравнивания трансферы.Раньше mips не разрешали, не знаю, что они делают сейчас.x86, 68K и т. д. были разрешены, и большую часть работы, возможно, приходилось выполнять контроллеру памяти.

Проекты, которые этого не допускают, явно предназначены для производительности и меньшего количества логики в том, что некоторые говорят, что это бремя для программистов, другие могут сказать, что это не дополнительная работа для программиста или облегчение для программиста.выровнены или нет, вы также можете понять, почему может быть лучше не пытаться экономить память, создавая 8-битные переменные, а идти вперед и записать 32-битное слово или любой другой естественный размер регистра или шины.Это может улучшить вашу производительность за небольшую плату в несколько байт.Не говоря уже о дополнительном коде, который компилятору нужно будет добавить, чтобы, скажем, 32-битный регистр имитировал 8-битную переменную, маскируя, а иногда и подписывая расширение.При использовании собственных размеров регистров эти дополнительные инструкции не требуются.Вы также можете упаковать несколько вещей в область шины/памяти и выполнить один цикл памяти для их сбора или записи, а затем использовать некоторые дополнительные инструкции для манипулирования между регистрами, не требуя затрат оперативной памяти, и возможной очистки количества инструкций.

Я не согласен с тем, что компилятор всегда выравнивает данные для цели, есть способы нарушить это.И если цель не поддерживает несогласованность, вы попадете в ошибку.Программистам никогда не приходилось бы говорить об этом, если бы компилятор всегда делал это правильно, основываясь на любом законном коде, который вы могли бы придумать, и для этого вопроса не было бы никаких причин, если бы он не касался производительности.если вы не контролируете выравнивание адреса void ptr или нет, вам придется все время использовать невыровненный доступ mem2() или вам придется выполнять if-then-else в своем коде на основе значения ptr как nik указал.объявив void, компилятор C теперь не сможет правильно обработать ваше выравнивание, и это не будет гарантировано.если вы возьмете char *prt и передадите его этим функциям, все ставки на то, что компилятор сделает все правильно, не будут добавлять дополнительный код, скрытый в функции mem2() или вне этих двух функций.так что, как написано в вашем вопросе, mem2() - единственный правильный ответ.

Скажем, DRAM, используемая на вашем настольном компьютере/ноутбуке, обычно имеет ширину 64 или 72 (с ecc) битами, и каждый доступ к ним выравнивается.Несмотря на то, что карты памяти на самом деле состоят из чипов шириной 8, 16 или 32 бита.(это может меняться в телефонах/планшетах по разным причинам) контроллер памяти и в идеале хотя бы один кэш находится перед этим драмом, так что обрабатываются невыровненные или даже выровненные доступы, меньшие, чем ширина шины чтения-изменения-записи. с кэш-памятью, которая намного быстрее, и все доступы к памяти выровнены по полной ширине шины.Если у вас нет кеша перед драмом, а контроллер предназначен для доступа к полной ширине, то это худшая производительность, если он предназначен для отдельного освещения байтовых дорожек (при условии, что чипы имеют ширину 8 бит), тогда у вас нет возможности чтения-модификации. -пишет но более сложный контроллер.если типичный вариант использования связан с кешем (если он есть в проекте), то, возможно, не имеет смысла выполнять эту дополнительную работу в контроллере для каждой байтовой полосы, но нужно, чтобы он просто знал, как выполнять передачи на полную ширину шины. или кратные.

_mem2 является более общим.Это будет работать независимо от того, выровнен ptr или нет._amem2 более строгий:для этого требуется, чтобы ptr был выровнен (хотя, по-видимому, это немного более эффективно).Поэтому используйте _mem2, если вы не можете гарантировать, что ptr всегда выровнен.

Многие процессоры имеют ограничения по выравниванию доступа к памяти.Невыровненный доступ либо генерирует прерывание исключения (например.ARM) или просто медленнее (например.х86).

_mem2 вероятно, реализуется как выборка двух байтов и использование сдвиговых и/или побитовых операций для создания из них 16-битного короткого замыкания.

_amem2 вероятно, просто читает 16-битный ushort из указанного PTR.

Я не знаю конкретно TMS320C64x, но предполагаю, что для 16-битного доступа к памяти требуется 16-битное выравнивание.Таким образом, вы можете использовать _mem2 всегда, но со снижением производительности, и _amem2 когда вы можете гарантировать, что ptr — четный адрес.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top