Ответ 1
Это не отвечает точке 2 (return a == d || b == d || c == d;
с той же скоростью, что и return false
). Это еще один интересный вопрос, так как он должен скомпилировать несколько инструкций с инструкциями типа uop-cache.
Тот факт, что 32-выровненная версия работает быстрее, мне странна, потому что [Intel руководство говорит, чтобы выровнять до 32]
Этот совет по оптимизации-рекомендации является очень общим руководством и, безусловно, не означает, что больше никогда не помогает. Обычно это не так, и отступы до 32 будут скорее ранить, чем помочь. (Промахи I-cache, пропуски ITLB и больше байтов кода для загрузки с диска).
Фактически, выравнивание 16B редко требуется, особенно на процессорах с кешем uop. Для небольшого цикла, который может выполняться из буфера цикла, выравнивание обычно не имеет значения.
16B по-прежнему не плох, как широкая рекомендация, но он не говорит вам все, что вам нужно знать, чтобы понять один конкретный случай на нескольких конкретных процессорах.
Компиляторы обычно по умолчанию выравнивают ветки цикла и точки входа функции, но обычно не выравнивают другие цели ветвления. Стоимость выполнения NOP (и разбухания кода) часто больше, чем вероятная стоимость невынастроенной цели ветвления без петли.
Выравнивание кода имеет некоторые прямые и некоторые косвенные эффекты. Прямые эффекты включают кэш uop в семействе Intel SnB. Например, см. Выравнивание ветвей для циклов с использованием микрокодированных инструкций для процессоров Intel SnB-семейства.
В другом разделе Руководство по оптимизации Intel подробно описано, как работает кеш uop:
2.3.2.2 Декодированный ICache:
- Все микрооперации в пути (строка кэша uop) представляют собой команды, которые являются статически смежными в коде и имеют свои EIP в пределах такой же выровненный 32-байтовый регион. (Я думаю, это означает указание, что проходит через границу, идет в кэше uop для блока содержащий его начало, а не конец. Инструкции по растягиванию должны пойти куда-нибудь, и целевой адрес ветки, который будет запускать инструкция является началом insn, поэтому наиболее полезно положить ее в строка для этого блока).
- Команда multi micro-op не может быть разделена между путями.
- Инструкция, которая включает MSROM, использует весь путь.
- Допускается использование до двух ветвей.
- Пара макроконфигурированных инструкций хранится как один микрооператор.
См. также Руководство микроаргата Agner Fog. Он добавляет:
- Безусловный переход или вызов всегда заканчиваются линией кэша μop
- много других вещей, которые, вероятно, здесь не актуальны.
Кроме того, если ваш код не подходит для кеша uop, он не может работать из буфера цикла.
Косвенные эффекты выравнивания включают:
- больший/меньший размер кода (пропуски кеша L1I, TLB). Не относится к вашему тесту.
- который связывает псевдонимы друг с другом в BTB (буфере целевых буферов).
Если я удалю
volatile
из one.cpp, код станет медленнее. Почему это?
Более крупные инструкции нажимают последнюю инструкцию в цикле через границу 32B:
59e: 83 eb 01 sub ebx,0x1
5a1: 75 dd jne 580 <main+0x20>
Итак, если вы не работаете из буфера цикла (LSD), то без volatile
один из циклов выборки uop-cache получает только 1 uop.
Если sub/jne макро-предохранители, это может не примениться. И я думаю, что только пересечение границы 64B нарушит макро-слияние.
Кроме того, это не настоящие адреса. Вы проверили, какие адреса после связывания? Там может быть граница 64B после компоновки, если текстовая секция имеет выравнивание менее 64B.
Извините, я на самом деле не проверял это, чтобы сказать больше об этом конкретном случае. Дело в том, что, когда вы сталкиваетесь с интерфейсом на стороне, например, с call
/ret
внутри жесткой петли, выравнивание становится важным и может стать чрезвычайно сложным. Пограничное пересечение или нет для всех будущих инструкций. Не ожидайте, что это будет просто. Если вы прочтете мои другие ответы, вы поймете, что я обычно не такой человек, чтобы сказать "это слишком сложно, чтобы полностью объяснить", но выравнивание может быть таким.
В вашем случае убедитесь, что крошечные функции встроены. Используйте оптимизацию времени соединения, если ваша кодовая база имеет какие-либо важные крошечные функции в отдельных файлах .c
, а не в .h
, где они могут встроить. Или измените свой код, чтобы поместить их в .h
.