Уточняют ли указатели на функции конвейер команд?

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

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

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

Я работаю на C, но я думаю, что это еще более важно в С++, где многие вызовы функций проходят через v-таблицы.

<ч/" > изменить

@JensGustedt пишет:

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

К сожалению, это может быть ловушка, в которую я попал.

Я написал целевую функцию малой и быстрой по соображениям производительности.

Но на него ссылается указатель на функцию, так что его можно легко заменить другими функциями (просто сделайте ссылку указателя другой функцией!). Поскольку я ссылаюсь на него с помощью указателя на функцию, я не думаю, что он может быть встроен.

Итак, у меня есть чрезвычайно краткая, не-встроенная функция.

Ответы

Ответ 1

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

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

Ответ 2

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

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

Я написал крошечное тело функции и измерил его в плотной петле миллионов звонков, чтобы определить стоимость только штрафа за вызов. "Встроенная" функция была контрольной группой, измеряющей только стоимость тела функции (в основном однопользовательский режим). Прямая функция измеряла штраф правильно спрогнозированной ветки (потому что это статическая цель и предиктор PPC всегда может получить это право) и пролог функции. Косвенная функция измеряла штраф в косвенной ветки bctrl.

614 400 000 вызовов функций:

inline:   411.924 ms  (   2 cycles/call )
direct:  3406.297 ms  ( ~17 cycles/call )
virtual: 8080.708 ms  ( ~39 cycles/call )

Как вы можете видеть, прямой вызов стоит 15 циклов больше, чем тело функции, а виртуальный вызов (в точности эквивалентен вызову указателя функции) стоит 22 цикла больше, чем прямой вызов. Это примерно соответствует тому, сколько стадий трубопровода существует между началом трубопровода (выборка команды) и концом ветки АЛУ. Поэтому по этой архитектуре косвенная ветвь (ака виртуальный вызов) приводит к отсутствию 22 этапов трубопровода 100% времени.

Другие архитектуры могут отличаться. Эти определения следует делать из прямых эмпирических измерений или из спецификаций конвейера ЦП, а не из предположений о том, какие процессоры должны "прогнозировать", потому что реализации настолько различны. В этом случае очистка трубопровода происходит потому, что предсказатель ветвления не знает, где будет идти bctrl, пока он не удалится. В лучшем случае он мог догадаться, что он соответствует той же цели, что и последний bctrl, и этот конкретный процессор даже не пытается это угадать.

Ответ 3

Там нет большой разницы между вызовом функции-указателя и "нормальным" вызовом, отличным от дополнительного уровня косвенности. Так что потенциально существует большая латентность; если адрес назначения еще не находится в кеше или регистре, тогда ЦП потенциально должен ждать, пока он будет извлечен из основной памяти.

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

Ответ 4

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

То, как современные "большие" ядра не в порядке, обрабатывают косвенные вызовы 1 примерно так:

  • После того, как вы выполнили непрямую ветвь несколько раз, предиктор непрямой ветвления попытается предсказать адрес, по которому ветвление произойдет в будущем.
  • Первые косвенные предсказатели ветвлений были очень простыми, способными "предсказывать" только одно фиксированное местоположение.
  • Более поздние предикторы, в том числе на большинстве современных процессоров, гораздо сложнее, часто способны хорошо предсказать повторяющийся паттерн косвенных скачков, а также соотнести цель скачка с направлением более ранних условных или косвенных ветвей.
  • Если прогноз успешен, непрямой вызов имеет стоимость, аналогичную обычному прямому вызову, и эта стоимость в значительной степени "вне линии" с остальной частью кода (т.е. Не участвует в цепочках зависимостей), поэтому влияние на конечное время выполнения кода, вероятно, будет небольшим, если вызовы не очень плотные.
  • С другой стороны, если прогноз окажется неудачным, вы получите полное неверное предсказание, подобное неправильному предсказанию направления ветвления. Вы не можете поставить фиксированное число на стоимость этого неверного прогноза, так как это зависит от окружающего кода, но обычно это вызывает пузырь около 20 циклов во внешнем интерфейсе, и общая стоимость во время выполнения часто заканчивается аналогично.

Итак, учитывая эти основы, мы можем сделать некоторые обоснованные предположения о том, что происходит в некоторых конкретных сценариях:

  1. Указатель на функцию всегда указывает на одну и ту же функцию, почти всегда 1 будет хорошо предсказуемым и будет стоить примерно столько же, сколько обычный вызов функции.
  2. Указатель на функцию, который случайно меняется между несколькими целями, почти всегда будет неверно предсказан. В лучшем случае мы можем надеяться, что предиктор всегда прогнозирует любую цель, которая является наиболее распространенной, поэтому в худшем случае, когда цели выбираются равномерно случайным образом между N целями, вероятность успеха прогнозирования ограничивается 1/N (т.е. Стремится к нулю как N уходит в бесконечность). В этом отношении непрямые ответвления ведут себя хуже в худшем случае, чем условные ответвления, которые, как правило, имеют уровень ошибочного прогнозирования в худшем случае 50% 2.
  3. Скорость прогнозирования для указателя на функцию с поведением где-то посередине, например, несколько предсказуемым (например, следуя повторяющейся схеме), будет сильно зависеть от деталей аппаратного обеспечения и сложности предсказателя. Современные чипы Intel имеют довольно хорошие косвенные предсказатели, но детали не были обнародованы. Традиционно считается, что они используют какой-то косвенный вариант предикторов TAGE, используемых также для условных переходов.

1 Случай, который мог бы предсказать даже для одной цели, включает в себя первый (или несколько раз) случай, когда функция встречается, так как предиктор не может предсказать косвенные вызовы, которых он еще не видел! Кроме того, размер ресурсов прогнозирования в ЦП ограничен, поэтому, если указатель функции не использовался в течение некоторого времени, в конечном итоге ресурсы прогнозирования будут использоваться для других ветвей, и в следующий раз вы вызовете неправильное прогнозирование.,

2 Действительно, очень простой условный предиктор, который просто предсказывает направление, наиболее часто встречающееся в последнее время, должен иметь 50% -ную степень предсказания в совершенно случайных направлениях ветвления. Чтобы получить значительно худший результат, чем 50%, вам нужно разработать состязательный алгоритм, который, по сути, моделирует предиктор и всегда выбирает переход в направлении, противоположном модели.