Выравнивание памяти на современных процессорах?

Я часто вижу код, например, следующий, когда, например, представляет большую растровую карту в памяти:

size_t width = 1280;
size_t height = 800;
size_t bytesPerPixel = 3;
size_t bytewidth = ((width * bytesPerPixel) + 3) & ~3; /* Aligned to 4 bytes */
uint8_t *pixelData = malloc(bytewidth * height);

(то есть растровое изображение, выделенное как непрерывный блок памяти, имеющий bytewidth, выровненный с определенным количеством байтов, чаще всего 4.)

Затем на изображении указывается точка:

pixelData + (bytewidth * y) + (bytesPerPixel * x)

Это приводит меня к двум вопросам:

  • Соответствует ли выравнивание такого буфера влиянию производительности на современные процессоры? Должен ли я вообще беспокоиться о выравнивании, или компилятор справится с этим?
  • Если это имеет какое-то влияние, может ли кто-нибудь указать мне на ресурс, чтобы найти идеальное выравнивание байтов для разных процессоров?

Спасибо.

Ответ 1

Это зависит от множества факторов. Если вы получаете доступ только к пиксельным данным по одному байту за раз, выравнивание не будет иметь никакого значения в подавляющем большинстве случаев. Для чтения/записи одного байта данных большинство процессоров не заботятся о том, находится ли этот байт на 4-байтной границе или нет.

Однако, если вы получаете доступ к данным в единицах, больших байта (скажем, в 2-байтных или 4-байтных единицах), вы обязательно увидите эффекты выравнивания. Для некоторых процессоров (например, для многих RISC-процессоров) совершенно невозможно получить доступ к неизмененным данным на определенных уровнях: попытка прочитать 4-байтовое слово из адреса, который не выровнен по 4 байтам, будет генерировать исключение доступа к данным (или исключение Хранения данных ) на PowerPC, например.

На других процессорах (например, x86) допускается доступ к неуравновешенным адресам, но часто это происходит со скрытым снижением производительности. Загрузка/хранение памяти часто реализуется в микрокоде, а микрокод будет обнаруживать неравномерный доступ. Обычно микрокод будет извлекать из памяти 4-байтовое количество, но если он не выровнен, ему нужно будет извлечь два 4-байтовых местоположения из памяти и восстановить требуемое 4-байтовое количество из соответствующих байтов двух местоположений. Захват двух мест памяти явно медленнее, чем один.

Это просто для простых загрузок и магазинов. Некоторые инструкции, например, в наборах команд MMX или SSE, требуют, чтобы их операнды памяти были правильно выровнены. Если вы попытаетесь получить доступ к неизмененной памяти с помощью этих специальных инструкций, вы увидите что-то вроде незаконного исключения инструкции.

Подводя итог, я бы не стал слишком беспокоиться о выравнивании, если вы не пишете супер-критически важный код (например, в сборке). Компилятор помогает вам много, например. путем добавления структур, так что 4-байтовые величины выравниваются по 4-байтным границам, а на x86 CPU также помогает вам справиться с неудовлетворенным доступом. Поскольку данные пикселов, с которыми вы имеете дело, находятся в количестве 3 байтов, вы почти всегда делаете однобайтовые обращения в любом случае.

Если вы решите, что вместо этого вы захотите получить доступ к пикселям в сингулярных 4-байтных доступах (в отличие от 3 однобайтовых доступов), было бы лучше использовать 32-битные пиксели и выровнять каждый отдельный пиксель на 4-байтовом граница. Выравнивание каждой строки до 4-байтовой границы, но не каждый пиксель будет иметь малое, если таковое имеет значение эффект.

Основываясь на вашем коде, я предполагаю, что это связано с чтением формата файла растрового изображения Windows. Растровые файлы требуют, чтобы длина каждой строки сканирования была кратной 4 байтам, поэтому настройка буферов данных пикселов с этим свойством свойство, которое вы можете просто прочитать во всем растровом изображении одним махом в свой буфер (конечно, вам все же приходится иметь дело с тем фактом, что строки сканирования хранятся снизу вверх, а не сверху вниз, и что пиксельные данные - это BGR вместо RGB). На самом деле это не очень выгодно, но это не намного труднее читать в растровой однострочной строке за раз.

Ответ 2

Да, выравнивание оказывает влияние на современные - пусть говорят x86 - процессоры. Как правило, нагрузки и запасы данных происходят на границах естественного выравнивания; если вы получите 32-битное значение в регистр, он будет самым быстрым, если он будет выровнен по 32-разрядной границе. Если это не так, x86 "позаботится об этом для вас", в том смысле, что процессор все равно будет выполнять нагрузку, но для этого потребуется значительно большее количество циклов, потому что будут внутренние споры с "переустановить" доступ.

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

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

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

Здесь есть более общая информация: http://en.wikipedia.org/wiki/Data_structure_alignment

(Специфические характеристики различаются между архитектурами, как в том, какими являются естественные выравнивания, независимо от того, обрабатывает ли процессор неуравновешенные нагрузки/хранилища автоматически и насколько они дороги. В тех случаях, когда процессор не справляется с доступом магически, часто время выполнения компилятора /C будет делать то, что он может сделать для вас.)

Ответ 3

  • Соответствует ли выравнивание такого буфера влиянию производительности на современные процессоры?

Да. Например, если memcpy оптимизирован с использованием инструкций SIMD (например, MMX/SSE), некоторые операции будут выполняться быстрее с выровненной памятью. В некоторых архитектурах есть (процессор) команды, которые терпят неудачу, если данные не выровнены, поэтому что-то может работать на вашем компьютере, но не в другом.

С помощью выровненных данных вы также лучше используете кэширование CPU.

  • Должен ли я вообще беспокоиться о выравнивании, или компилятор справится с этим?

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

Для других вещей в вашем коде вы используете флаг -malign и выровненный атрибут.

Ответ 4

Буферное выравнивание оказывает влияние. Вопрос в том, является ли это значительным воздействием? Ответ может быть высоко для конкретного приложения. В архитектурах, которые не поддерживают независимый доступ, например, 68000 и 68010 (68020 добавляет неприсоединенный доступ) - это действительно проблема производительности и/или обслуживания, поскольку процессор будет виноват или, может быть, ловушка для обработчика для выполнения неравномерного доступа.

Можно оценить идеальное выравнивание для различных процессоров: 4-байтовое выравнивание подходит для архитектур с 32-битным трактом данных. 8-байтовое выравнивание для 64-битного. Тем не менее, L1 имеет эффект кэширования. Для многих процессоров это 64 байта, хотя это, несомненно, изменится в будущем.

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

Ваш пример довольно своеобразен: 3-байтовые элементы имеют 50% -ный шанс индивидуально не выравниваться (до 32 бит), поэтому выравнивание буфера кажется бессмысленным - по крайней мере, по соображениям производительности. Однако в случае массовой передачи всего этого, он оптимизирует первый доступ. Обратите внимание, что неравнозначный первый байт может также иметь влияние производительности при передаче на видеоконтроллер.