Современная косвенная оптимизация внутреннего цикла ЦП

Из http://www.boost.org/community/implementation_variations.html

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

Я пытаюсь понять "даже во внутреннем цикле" часть. Конкретно, что механизмы центральные процессоры реализуют для выполнения этих двух кодов (виртуальный по сравнению с невиртуальным или дополнительным уровнем абстракции) в том же количестве тактов? Я знаю о конвейерной обработке инструкции и кэшировании, но как возможно выполнить виртуальный вызов в том же количестве тактов как невиртуальный вызов? Как косвенность "потеряна"?

7
задан amit 15 August 2010 в 15:13
поделиться

5 ответов

Кэширование (например, кэширование цели ветвления ), параллельные единицы загрузки (часть конвейерной обработки, но также такие вещи, как «попадание при промахе», которые не останавливают конвейер), и выполнение вне очереди , вероятно, поможет преобразовать нагрузку - нагрузку - ветвь во что-то более близкое к фиксированному филиал .Сворачивание / исключение инструкций (какой правильный термин для этого?) На этапе декодирования или предсказания ветвления конвейера также может способствовать.

Все это зависит от множества разных вещей: сколько существует различных целей ветвления (например, сколько различных виртуальных перегрузок вы, вероятно, вызовете), сколько вещей вы перебираете (является ли целевой кеш ветвления) теплый »? как насчет icache / dcache?), как виртуальные таблицы или косвенные таблицы размещаются в памяти (удобны ли они для кеширования или каждая новая загрузка vtable может вытеснять старую vtable?), аннулируется ли кеш неоднократно из-за многоядерного пинг-понга и т. д.

(Заявление об ограничении ответственности: я определенно не эксперт, и многие мои знания получены при изучении упорядоченных встроенных процессоров, поэтому некоторые из них являются экстраполяцией. Если у вас есть исправления, не стесняйтесь комментировать!)

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


Редактировать:

Как указывает Ханс Пассан в вышеприведенном комментарии Оптимизация косвенного обращения к внутреннему циклу современного процессора , ключом к тому, чтобы эти две вещи занимали одинаковое количество времени, является способность эффективно " удалите »более одной инструкции за цикл. Исключение инструкций может помочь в этом, но суперскалярный дизайн , вероятно, более важен (попадание при промахе - очень маленький и конкретный пример, полностью резервированные единицы нагрузки могут быть лучше).

Давайте возьмем идеальную ситуацию и предположим, что прямая ветвь - это всего одна инструкция:

branch dest

... а косвенная ветвь - три (может быть, вы можете получить ее из двух, но она больше одной):

load vtable from this
load dest from vtable
branch dest

Предположим, что это абсолютно идеальная ситуация: * эта и вся vtable находятся в кэше L1, кэш L1 достаточно быстр, чтобы поддерживать амортизированную стоимость одного цикла на инструкцию для двух загрузок. (Вы даже можете предположить, что процессор переупорядочил нагрузки и смешал их с более ранними инструкциями, чтобы дать им время для их завершения до перехода; это не имеет значения для этого примера.) Также предположим, что целевой кеш ветвления является горячим и нет конвейера стоимость очистки для ветви, а инструкция перехода сводится к одному циклу (амортизируется).

Следовательно, теоретическое минимальное время для первого примера составляет 1 цикл (с амортизацией).

Теоретический минимум для второго примера, при отсутствии исключения инструкций или избыточных функциональных модулей или чего-то, что позволит исключить более одной инструкции за цикл, составляет 3 цикла (есть 3 инструкции)!

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

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

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

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

Вы можете просто поддерживать небольшую паранойю по поводу виртуалов. См. статью Ноэля Ллописа о дизайне, ориентированном на данные , прекрасные слайды Ловушки объектно-ориентированного программирования и сварливые, но образовательные презентации Майка Эктона . Теперь вы внезапно перешли к шаблонам, которые, вероятно, уже устраивают ЦП, если вы обрабатываете много данных.

Функции языка высокого уровня, такие как виртуальный, обычно представляют собой компромисс между выразительностью и контролем. Я честно думаю, однако, что, просто увеличивая вашу осведомленность о том, что на самом деле делает virtual (не бойтесь время от времени читать представление разборки и определенно заглядывать в руководства по архитектуре вашего процессора), вы, скорее всего, будете его использовать. когда это имеет смысл, а не когда нет, а профилировщик может покрыть все остальное, если это необходимо.

Универсальные утверждения о том, что «не используйте виртуальное» или «виртуальное использование вряд ли будет иметь измеримое значение», делают меня ворчливым. Реальность обычно более сложна, и либо вы окажетесь в ситуации, когда вы достаточно заботитесь, чтобы профилировать или избегать этого, либо вы находитесь в тех других 95%, где, вероятно, не стоит заботиться, за исключением возможного образовательного контента.

4
ответ дан 7 December 2019 в 03:09
поделиться

Конвейер - это основной способ.

Для загрузки инструкции, ее декодирования, выполнения действий и загрузки косвенных ссылок на память может потребоваться 20 тактов. Но из-за конвейерной линии процессор может выполнять части 19 других инструкций одновременно на разных этапах конвейера, обеспечивая общую пропускную способность в 1 инструкцию за каждый тактовый цикл, независимо от того, сколько времени фактически требуется для подачи этой инструкции через конвейер.

3
ответ дан 7 December 2019 в 03:09
поделиться

Современные процессоры используют технику адаптивного прогнозирования переходов, которая может прогнозировать множество косвенных переходов, таких как vtable реализация виртуальных функций. См. http://en.wikipedia.org/wiki/Branch_prediction#Prediction_of_indirect_jumps

1
ответ дан 7 December 2019 в 03:09
поделиться

Если процессор уже имеет адрес памяти в кэше, то выполнение инструкции загрузки тривиально, если это.

0
ответ дан 7 December 2019 в 03:09
поделиться

Я думаю, что происходит то, что у процессора есть специальный кеш, в котором хранятся местоположения и цели ветвей и непрямых переходов. Если косвенный переход встречается по адресу $ 12345678, и в последний раз он перешел по адресу $ 12348765, процессор может начать спекулятивное выполнение инструкций по адресу $ 12348765 даже до того, как он разрешит адрес ветви. Во многих случаях внутри внутреннего цикла функции конкретный косвенный переход всегда будет переходить на один и тот же адрес на протяжении всего цикла. Таким образом, кэш косвенного перехода может избежать штрафов за ветвление.

1
ответ дан 7 December 2019 в 03:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: