Почему нарушение "выходной зависимости" LZCNT имеет значение?

При тестировании чего-либо я измерил намного более низкую пропускную способность, чем рассчитывал, и сузил до инструкции LZCNT (это также происходит с TZCNT), как показано в следующих тестах:

  xor ecx, ecx
_benchloop:
  lzcnt eax, edx
  add ecx, 1
  jnz _benchloop

А также:

  xor ecx, ecx
_benchloop:
  xor eax, eax  ; this shouldn't help, but it does
  lzcnt eax, edx
  add ecx, 1
  jnz _benchloop

Вторая версия намного быстрее. Так не должно быть. Нет причин, по которым LZCNT должен иметь входную зависимость от своего вывода. В отличие от BSR/BSF, инструкции xZCNT всегда перезаписывают свои выходные данные.

Я использую это на 4770K, поэтому LZCNT и TZCNT не выполняются как BSR/BSF.

Что тут происходит?

2 ответа

Решение

Это просто ограничение в микроархитектуре вашего процессора Intel Haswell и нескольких предыдущих 1 процессоров. Это было исправлено для tzcnt а также lzcnt на Skylake, но проблема остается для popcnt,

На этих микроархитектурах целевой операнд для tzcnt, lzcnt а также popcnt обрабатывается как входная зависимость, хотя семантически это не так. Теперь я сомневаюсь, что это действительно "ошибка": если бы это был просто недосмотр, я ожидаю, что это было бы исправлено в одной из нескольких новых микроархитектур, которые были выпущены с момента ее появления.

Скорее всего, это компромисс дизайна, основанный на одном или обоих из следующих двух факторов:

  • Аппаратное обеспечение для popcnt, lzcnt а также tzcnt скорее всего все поделено с существующим bsf а также bsr инструкции. Сейчас bsf а также bsr действительно имел зависимость от предыдущего значения назначения на практике 2 для особого случая ввода все биты с нулем, поскольку чипы Intel оставили назначение без изменений в этом случае. Таким образом, вполне возможно, что простейшая схема комбинированного оборудования привела к тому, что другие аналогичные инструкции выполнялись на том же модуле, наследуя ту же зависимость.

  • Подавляющее большинство инструкций ALU с двумя операндами x86 зависит от операнда-получателя, поскольку он также используется в качестве источника. Три затронутые инструкции являются несколько уникальными в том смысле, что они являются унарными операторами, но в отличие от существующих унарных операторов, таких как not а также neg у которых есть один операнд, используемый в качестве источника и назначения, у них есть различные операнды источника и назначения, что делает их внешне похожими на большинство команд с двумя входами. Возможно, схема переименования / планировщика просто не различает особый случай этих унарных с двумя регистрами-операндами по сравнению с подавляющим большинством простых общих команд ввода-вывода источника / назначения, которые не имеют этой зависимости.

На самом деле, для случая popcnt Корпорация Intel выпустила различные исправления, посвященные проблеме ложных зависимостей, такие как HSD146 для Haswell Desktop и SKL029 для Skylake, которая гласит:

Инструкция POPCNT может занять больше времени, чем ожидалось

Проблема Выполнение команды POPCNT с 32- или 64-битным операндом может быть отложено до тех пор, пока не будут выполнены предыдущие независимые инструкции.

Программное обеспечение Implication, использующее инструкцию POPCNT, может иметь более низкую производительность, чем ожидалось.

Обходной путь Не определено

Я всегда находил эту ошибку необычной, поскольку она на самом деле не идентифицирует какой-либо тип функционального дефекта или несоответствие спецификации, которая имеет место по существу для всех других ошибок. Корпорация Intel на самом деле не документирует конкретную модель производительности для механизма выполнения OoO, и за эти годы появилось и исчезло множество других "проблем" производительности (многие из которых имеют гораздо большее влияние, чем эта незначительная проблема), которые не документально подтверждено. Тем не менее, это, возможно, дает некоторые доказательства того, что это можно считать ошибкой. Как ни странно, опечатка никогда не была расширена, чтобы включить tzcnt или же lzcnt который имел ту же проблему, когда они были введены.


1 хорошо tzcnt а также lzcnt только появился в Haswell, но проблема существует для popcnt также, что было введено в Nehalem - но проблема ложной зависимости, возможно, существует только для Sandy Bridge или позже.

2 На практике, хотя и не задокументировано в документации по ISA, поскольку результат для ввода "все ноль" не был определен в руководствах Intel. Однако большинство или все микросхемы Intel реализовали такое поведение, что в этом случае регистр назначения остается неизменным.

В соответствии с тем, что предложил @BrettHale, возможно (если нечетно), что вы попали в стойку обновления частичных флагов в угловом регистре. Состояние флага теоретически должно быть просто переименовано, потому что следующее добавление обновляет все флаги, но если это не по какой-то причине, то это вводит зависимость, переносимую циклом, и вставка xor нарушит эту зависимость.

Трудно сказать наверняка, происходит ли так, но это выглядит случайным взглядом, чтобы быть наиболее вероятным объяснением; Вы можете проверить гипотезу, заменив xor с test (который также нарушает зависимость флагов, но не влияет на зависимости регистра).

Другие вопросы по тегам