Почему медленная инструкция цикла? Не удалось ли Intel эффективно внедрить его?

LOOP ( запись в ручном режиме Intel ref ) уменьшает ecx / rcx, а затем перескакивает, если отличная от нуля . Это медленный процесс, но Intel не могла быстро сделать это быстро? dec/jnz уже макро-предохранители в один uop на семье Sandybridge; с той лишь разницей, что это устанавливает флаги.

loop на различных микроархитектурах, из таблиц инструкций Agner Fog :


Не могли ли декодеры просто декодировать то же, что и lea rcx, [rcx-1] / jrcxz ? Это будет 3 часа. По крайней мере, это будет иметь место без префикса размера адреса, в противном случае он должен использовать ecx и обрезать RIP для EIP если EIP переход; возможно, странный выбор размера адреса, управляющего шириной декремента, объясняет многие ошибки?

Или лучше, просто расшифруйте его как плавкую де-и-ветку, которая не устанавливает флаги? dec ecx / jnz на SnB декодирует до одного uop (который устанавливает флаги).

Я знаю, что настоящий код не использует его (потому что он был медленным, так как по крайней мере P5 или что-то еще), но AMD решила, что стоит быстро сделать это для Bulldozer. Наверное, потому что это было легко.


  • Было бы легко, чтобы уарм семейства SnB имел быструю loop ? Если да, то почему? Если нет, то почему это сложно? Много транзисторов декодера? Или дополнительные биты в объединенном dec & branch uop для записи, что он не устанавливает флаги? Что могли бы сделать эти 7? Это действительно простая инструкция.

  • Что особенного в Bulldozer, который сделал быстрый loop легким / стоит? Или AMD потратила кучу транзисторов на быстрый loop ? Если это так, то, по-видимому, кто-то думал, что это хорошая идея.


Если бы loop был быстрым , это было бы идеально для целых циклов adc с произвольной точностью BigInteger, чтобы избегать партитур / adc с частичным флагом (см. Мои комментарии к моему ответу) или любого другого случая, когда вы хотите зацикливать, не касаясь флагов. Он также имеет незначительное преимущество по сравнению с dec/jnz . (И dec/jnz только макро-предохранители на семействе SnB).

На современных процессорах, где dec/jnz в порядке в цикле ADC, loop все равно будет приятным для циклов ADCX / ADOX (для сохранения OF).

Если бы loop был быстрым, компиляторы уже использовали бы его как оптимизацию подкачки для скорости кода + скорость на процессорах без макро-слияния.


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

Теперь, после того как я начал писать свой вопрос, он оказался точным дубликатом одного на comp.arch , который сразу же появился. Я ожидал, что это будет сложно для google (много «почему мой цикл медленный»), но моя первая попытка ( why is the x86 loop instruction slow ) получила результаты.

Это не хороший или полный ответ.

Это может быть лучшее, что мы получим, и вам будет необходимо, если кто-то не сможет пролить свет на него. Я не собирался писать это как ответ на свой вопрос.


Хорошие сообщения с различными теориями в этом streamе:

Роберт

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


Антон Эртл :

IIRC LOOP использовался в некоторых программах для циклов синхронизации; было (важное) программное обеспечение, которое не работало на процессорах, где LOOP был слишком быстрым (это было в начале 90-х или около того). Поэтому производители процессоров научились замедлять работу LOOP.


(Пол и кто-либо еще: вы можете повторно написать свое собственное письмо в качестве своего собственного ответа. Я удалю его из своего ответа и проголосую за вас).

@Paul A. Clayton (случайный плакат SO и разработчик архитектуры процессора) угадал, как вы можете использовать это множество . (Это выглядит как loope/ne который проверяет как счетчик, так и ZF):

Я мог представить, возможно, разумную версию с 6 мкФ:

 virtual_cc = cc; temp = test (cc); rCX = rCX - temp; // also setting cc cc = temp & cc; // assumes branch handling is not // substantially changed for the sake of LOOP branch cc = virtual_cc 

(Обратите внимание, что это 6 uops, а не SnB’s 11 для LOOPE / LOOPNE, и это общее предположение, даже не пытаясь принять во внимание все, что известно из счетчиков SnB perf.)

Тогда Павел сказал:

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

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

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

(Мое мнение: Intel, вероятно, все еще делает это медленно, и не позаботился переписать свой микрокод для него в течение длительного времени. Современные процессоры, вероятно, слишком быстр для чего-либо, используя loop наивным образом, чтобы работать правильно.)

… Павел продолжает:

Архитекторы позади Nano, возможно, обнаружили, что избегать специальной оболочки LOOP упростили их дизайн с точки зрения площади или мощности. Или у них могут быть стимулы для встроенных пользователей, чтобы обеспечить быструю реализацию (для преимуществ плотности кода). Это всего лишь УИЛД .

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

Я подозреваю, что такие решения основаны на конкретных деталях реализации. Информация о таких деталях, как представляется, не является общедоступной, и интерпретация такой информации будет выше уровня квалификации большинства людей. (Я не разработчик аппаратного обеспечения – и никогда не играл на телевидении или не оставался в Holiday Inn Express. 🙂


Затем нить пошла в тему AMD в духе нашей единственной возможности почистить крип в кодировке команд x86. Их сложно обвинить, поскольку каждое изменение – это случай, когда декодеры не могут делиться транзисторами. И до того, как Intel приняла x86-64, было даже не ясно, что это будет ловить. AMD не хотела обременять свои процессоры аппаратурой, которую никто не использовал, если AMD64 не поймал.

Но все же, есть так много мелочей: setcc мог быть изменен до 32 бит. (Обычно вам нужно использовать xor-zero / test / setcc, чтобы избежать ложных зависимостей, или потому, что вам нужен реестр с нулевой расширенностью). Shift может иметь безоговорочно записанные флаги, даже с нулевым сдвигом (удаление зависимости входных данных от eflags для смены переменных-счетчиков для исполнения LLC). В прошлый раз, когда я набрал этот список домашних животных, я думаю, что был третий … О да, bt / bts и т. Д. С операндами памяти имеет адрес, зависящий от верхних бит индекса (битовая строка, а не только бит внутри машинное слово).

bts очень полезны для бит-полей и медленнее, чем они должны быть, поэтому вы почти всегда хотите загрузить в регистр, а затем использовать это. (Обычно быстрее bts [mem], reg / маскировать, чтобы получить адрес самостоятельно, вместо того, чтобы использовать 10 bts [mem], reg на Skylake, но он требует дополнительных инструкций, поэтому он имеет смысл на 386, но не на K8). Атомная бит-манипуляция должна использовать форму памяти-dest, но в версии с lock версией в любом случае нужно много uops. Он по-прежнему медленнее, чем если бы он не мог получить доступ за пределами слова, на dword он работает.

Пожалуйста, см. Хорошую статью Абраша, Майкла, опубликованную в журнале Dr. Dobb’s March 1991 v16 n3 p16 (8): http://archive.gamedev.net/archive/reference/articles/article369.html

Резюме статьи следующее:

Оптимизация кода для микропроцессоров 8088, 80286, 80386 и 80486 затруднена, потому что чипы используют значительно разные архитектуры памяти и время выполнения команд. Код не может быть оптимизирован для семейства 80×86; скорее, код должен быть разработан для обеспечения хорошей производительности в ряде систем или оптимизирован для конкретных комбинаций процессоров и памяти. Программисты должны избегать необычных инструкций, поддерживаемых 8088, которые потеряли свою производительность в последующих чипах. Строковые инструкции следует использовать, но не полагаться. Следует использовать регистры, а не операции с памятью. Ветвление также медленное для всех четырех процессоров. Доступ к памяти должен быть выровнен для повышения производительности. Как правило, оптимизация 80486 требует совершенно противоположных шагов по оптимизации 8088.

Под «необычными инструкциями, поддерживаемыми 8088» автор также означает «цикл»:

Любой программист 8088 инстинктивно заменит: DEC CX JNZ LOOPTOP с: LOOP LOOPTOP, потому что LOOP значительно быстрее на 8088. LOOP также быстрее на 286. На 386, однако, LOOP на самом деле на два цикла медленнее, чем DEC / JNZ. Маятник качается еще дальше на 486, где LOOP примерно в два раза медленнее, чем DEC / JNZ, – и, разумеется, мы говорим о том, что изначально было наиболее очевидной оптимизацией во всем наборе команд 80×86.

Это очень хорошая статья, и я очень рекомендую ее. Несмотря на то, что он был опубликован в 1991 году, это на удивление очень актуально сегодня.

Но эта статья просто дает советы, она рекомендует проверять скорость выполнения и выбирать более быстрые варианты. Это не объясняет, почему некоторые команды становятся очень медленными, поэтому он не полностью решает ваш вопрос.

Ответ заключается в том, что более ранние процессоры, такие как 80386 (выпущенные в 1985 году) и ранее, последовательно выполняли инструкции один за другим.

Позже процессоры начали использовать конвейерную обработку команд – изначально, просто, для 804086, и, наконец, Pentium Pro (выпущенный в 1995 году) представил принципиально другой внутренний трубопровод, назвав его kernelм Out Of Order (OOO), где инструкции были преобразованы в небольшие fragmentы операций, называемых микрооперациями или μops, а затем все микрооперации разных инструкций были помещены в большой пул микроопераций, где они должны были выполняться одновременно, если они не зависят друг от друга. Этот принцип трубопровода ООО по-прежнему практически не меняется на современных процессорах. Более подробную информацию о конвейерной обработке инструкций вы можете найти в этой блестящей статье: https://www.gamedev.net/resources/_/technical/general-programming/a-journey-through-the-cpu-pipeline-r3115

Чтобы упростить разработку чипов, Intel решила построить процессоры таким образом, что одна инструкция очень эффективно трансформировалась в микрооператоры, а другие – нет.

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

Например, в «Справочном руководстве по оптимизации архитектуры Intel®» http://download.intel.com/design/PentiumII/manuals/24512701.pdf упоминается следующее: «Избегайте использования сложных инструкций (например, введите, оставьте или зациклируйте ), которые обычно имеют более четырех микросхем и требуют нескольких циклов для декодирования. Вместо этого используйте последовательности простых инструкций. ”

Итак, Intel почему-то решила, что инструкция «loop» является «сложной», и с тех пор она стала очень медленной. Тем не менее, нет официальной ссылки Intel на разбивку команд: сколько микроопераций выдает каждая инструкция и сколько циклов требуется для ее декодирования.

Вы также можете прочитать о механизме выполнения вне очереди в «Справочном руководстве по оптимизации архитектуры Intel® 64 и IA-32» http://www.intel.com/content/dam/www/public/us/en/ документы / руководства / 64-ia-32-architecture-optimization-manual.pdf раздел 2.1.2.

  • Использование разных версий одной и той же сборки в одной папке
  • Visual Studio «Не удалось скопировать» ... во время сборки
  • Как определить, была ли assembly .NET построена для x86 или x64?
  • Как использовать строки в emu8086
  • Проверьте, равен ли регистр нулю с помощью CMP reg, 0 против OR reg, reg?
  • Как сделать kernel ​​для моего загрузчика?
  • Возможно ли «декомпилировать» Windows .exe? Или, по крайней мере, рассмотреть Ассамблею?
  • Медленная инструкция jmp
  • Visual Studio 2010: ссылочные сборки Ориентация на версию с более высокой версией
  • Не удалось загрузить файл или сборку HRESULT: 0x80131515 (При добавлении controllerа в проект MVC с ссылками на сборку на сетевом диске)
  • Возможно ли одноуровневое многоязычное развертывание Windows Forms (ILMerge и спутниковые сборки / локализация)?
  • Давайте будем гением компьютера.