Выравнивание памяти на современных процессорах?
Я часто вижу код, например, следующий, когда, например, представляет большую растровую карту в памяти:
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 бит), поэтому выравнивание буфера кажется бессмысленным - по крайней мере, по соображениям производительности. Однако в случае массовой передачи всего этого, он оптимизирует первый доступ. Обратите внимание, что неравнозначный первый байт может также иметь влияние производительности при передаче на видеоконтроллер.