Почему нарушение "выходной зависимости" 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.

Что здесь происходит?

Ответ 1

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

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

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

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

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

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

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

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

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

Обход проблемы Не указано

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


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

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

Ответ 2

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

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