Проблема не ограничивается только выборками команд. И, к сожалению, программисты не знают об этом рано и часто наказывают за это. Архитектура x86 сделала людей ленивыми. Это затрудняет переход на другие архитектуры.
Это имеет прямое отношение к природе шины данных. Если у вас есть, например, шина данных шириной 32 бита, чтение по памяти выравнивается по этой границе. В этом случае два младших адресных бита обычно игнорируются, поскольку они не имеют значения. Так что если вам нужно выполнить 32-битное чтение с адреса 0x02, будь то часть выборки команд или чтение из памяти. Затем требуются два цикла памяти: чтение с адреса 0x00 для получения двух байтов и чтение с 0x04 для получения двух других байтов. Взятие вдвое дольше, остановка конвейера, если это выборка команды. Снижение производительности является существенным и ни в коем случае не является потраченной впустую оптимизацией для чтения данных. Программы, которые выравнивают свои данные по естественным границам и корректируют структуры и другие элементы в целочисленных кратных этих размерах, могут увидеть производительность в два раза больше без каких-либо других усилий. Аналогично, использование int вместо char для переменной, даже если она рассчитывает только до 10, может быть быстрее. Это правда, что добавление nops в программы для выравнивания мест назначения веток обычно не стоит усилий. К сожалению, x86 имеет переменную длину слова, основанную на байтах, и вы постоянно страдаете от этой неэффективности. Если вы закрашены в угол и вам нужно выжать еще несколько тактов из цикла, вам следует выровнять не только границу, соответствующую размеру шины (в наши дни 32 или 64 бит), но также и границу строки кэша, и попытайтесь сохранить этот цикл в пределах одной или двух строк кэша. На этом примечании единственный случайный nop в программе может вызвать изменения, где строки кэша попали, и изменение производительности может быть обнаружено, если программа достаточно велика и имеет достаточно функций или циклов. Та же самая история, скажем, например, у вас есть цель ветвления по адресу 0xFFFC, если не в кеше должна быть извлечена строка кэша, ничего неожиданного, но одна или две инструкции спустя (четыре байта) требуется другая строка кэша. Если цель была 0x10000, в зависимости от размера вашей функции, естественно, вы могли бы выполнить это в одной строке кэша. Если это часто вызываемая функция, а другая часто вызываемая функция находится по достаточно похожему адресу, чтобы эти два выселяли друг друга, вы будете запускаться в два раза медленнее. Это место, где x86 помогает, хотя с переменной длиной инструкции вы можете упаковать больше кода в строку кэша, чем на других хорошо используемых архитектурах.
С x86 и инструкциями вы не сможете по-настоящему выиграть. На этом этапе часто бесполезно пытаться настроить программы x86 (с точки зрения инструкций). Количество разных ядер и их нюансы вы можете получить на одном процессоре на одном компьютере за один день, но этот же код заставит другие процессоры x86 на других компьютерах работать медленнее, иногда менее чем вдвое быстрее. Лучше быть в целом эффективным, но иметь некоторую неряшливость, чтобы он работал нормально на всех компьютерах каждый день. Выравнивание данных покажет улучшение между процессорами на разных компьютерах, но выравнивание команд не будет.