Медленная инструкция jmp

В соответствии с моим вопросом . Преимущества использования 32-битных регистров / инструкций в x86-64 я начал измерять затраты инструкций. Я знаю, что это было сделано несколько раз (например, Agner Fog ), но я делаю это для удовольствия и самообразования.

Мой тестовый код довольно прост (для простоты здесь как псевдокод, на самом деле в ассемблере):

for(outer_loop=0; outer_loop<NO;outer_loop++){ operation #first operation #second ... operation #NI-th } 

Но все же нужно учитывать некоторые вещи.

  1. Если внутренняя часть цикла велика (большая NI>10^7 ), все содержимое цикла не вписывается в кеш команд и, следовательно, должно загружаться снова и снова, делая скорость ОЗУ, определяющей необходимое время для выполнения. Например, для больших внутренних частей xorl %eax, %eax (2 байта) на 33% быстрее, чем xorq %rax, %rax (3 байта).
  2. Если NI мал и весь цикл легко вписывается в кеш команд, то xorl %eax, %eax и xorq %rax, %rax одинаково быстр и могут выполняться 4 раза за такт.

Однако эта простая модель не содержит воды для jmp -конструкции. Для jmp -instruction мой тестовый код выглядит следующим образом:

 for(outer_loop=0; outer_loop<NO;outer_loop++){ jmp .L0 .L0: jmp .L1 L1: jmp L2 .... } 

И результаты:

  1. Для «больших» размеров контура (уже для NI>10^4 ) я измеряю 4.2 ns / jmp -instruction (будет равняться 42 байтам, загруженным из ОЗУ или приблизительно 12 тактов на моей машине).
  2. Для небольших размеров петли ( NI<10^3 ) я измеряю 1 ns / jmp- инструкцию (которая составляет около 3 тактов, что звучит правдоподобно). Таблицы Agner Fog показывают затраты в 2 тактовых цикла).

Команда jmp LX использует 2 байта eb 00 кодирования.

Таким образом, мой вопрос: что может быть объяснением высокой стоимости jmp -инструкции в «больших» циклах?

PS: Если вы хотите попробовать это на своей машине, вы можете скачать скрипты здесь , просто запустите sh jmp_test.sh в src- папке.


Edit: Экспериментальные результаты, подтверждающие теорию размера BTB Питера.

В следующей таблице показаны циклы для каждой команды для разных значений ǸI (относительно NI = 1000):

 |oprations/ NI | 1000 | 2000| 3000| 4000| 5000| 10000| |---------------------|------|------|------|------|------|------| |jmp | 1.0 | 1.0 | 1.0 | 1.2 | 1.9 | 3.8| |jmp+xor | 1.0 | 1.2 | 1.3 | 1.6 | 2.8 | 5.3| |jmp+cmp+je (jump) | 1.0 | 1.5 | 4.0 | 4.4 | 5.5 | 5.5| |jmp+cmp+je (no jump) | 1.0 | 1.2 | 1.3 | 1.5 | 3.8 | 7.6| 

Это можно увидеть:

  1. Для команды jmp ресурс (пока неизвестный) становится недостаточным, и это приводит к ухудшению производительности для ǸI больше 4000.
  2. Этот ресурс не разделяется с такими инструкциями, как xor – снижение производительности приводит к тому, что для NI около 4000, если jmp и xor выполняются друг за другом.
  3. Но этот ресурс делится с je если происходит переход – для jmp + je друг для друга ресурс становится дефицитным для NI около 2000.
  4. Однако, если je вообще не прыгает, ресурс снова становится скудным, для NI около 4000 (4-я строка).

В материалах реверсивного предсказания ветвления-предсказания Мэтта Годбольта установлено, что емкость целевого буфера ответвления составляет 4096 записей. Это очень убедительное доказательство того, что промахи BTB являются причиной наблюдаемой разницы пропускной способности между маленькими и большими петлями jmp .

TL: DR: моя текущая догадка заканчивается записями BTB (ветви целевого буфера). Смотри ниже.


Несмотря на то, что ваши jmp s не являются операционными системами, у CPU нет дополнительных транзисторов для обнаружения этого особого случая. Они обрабатываются так же, как и любой другой jmp , что означает необходимость повторного запуска команды из нового места, создавая пузырь в конвейере.

Чтобы узнать больше о прыжках и их влиянии на конвейерные процессоры, опасности управления в classическом конвейере RISC должны быть хорошим вводом тому, почему ветви сложны для конвейерных процессоров. Руководства Агнера Фога объясняют практические последствия, но я думаю, что некоторые из этих базовых знаний.


У вашего процессора Intel Broadwell есть uop-cache , который кэширует декодированные инструкции (отдельно от 32kB L1 I-cache).

Размер кеша uop – 32 набора из 8 способов, с 6 выводами в каждой строке, в общей сложности 1536 мкп (если каждая строка упакована с 6 мкп, отличная эффективность). 1536 часов находится между вашими 1000 и 10000 размерами теста. Перед вашим редактированием я предсказал, что обрезание для медленного ускорения будет равно 1536 общих инструкций в вашем цикле. Он не замедляется вообще до тех пор, пока не будет выполнено более 1536 инструкций, поэтому я думаю, что мы можем исключить эффекты uop-cache. Это не такой простой вопрос, как я думал. 🙂

Выполнение из uop-кеша (размер небольшого кода) вместо декодеров команд x86 (больших циклов) означает, что на этапе, где распознаются команды jmp , меньше ступеней конвейера. Таким образом, мы могли бы ожидать, что пузыри из постоянного streamа скачков будут меньше, даже если они предсказаны правильно.

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


Несмотря на то, что ЦП не требует предсказать, занята ли ветка или нет, она все равно может использовать ресурсы outlookирования ветвей, чтобы предсказать, что блок кода содержит взятую ветвь до ее декодирования.

Кэширование того факта, что есть ветвь в определенном блоке кода и его целевой адрес, позволяет интерфейсу начинать выборку кода из целевого объекта ветвления до того, jmp rel32 кодировка jmp rel32 фактически декодируется. Помните, что инструкции x86 с расширением переменной длины сложны: вы не знаете, где начинается одна команда, пока не будет декодирована предыдущая. Таким образом, вы не можете просто сопоставить шаблон с streamом команд, который ищет безусловные переходы / вызовы, как только он будет извлечен.

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

См. Также Какое неверное предсказание отрасли обнаруживает обнаружение буфера целевого буфера? который имеет приятный ответ и обсуждение в этом streamе Realworldtech .

Один очень важный момент: BTB предсказывает, в каком блоке выбрать следующий, а не точную точку назначения конкретной ветви в блоке выборки. Поэтому вместо того, чтобы outlookировать цели для всех ветвей в блоке выборки, CPU просто должен предсказать адрес следующей выборки.


Да, пропускная способность памяти может быть узким местом при работе с очень высокой пропускной способностью, такой как xor-zeroing, но вы сталкиваетесь с другим узким местом с jmp . CPU успеет извлечь 42B из памяти, но это не то, что он делает. Предварительная выборка может легко идти в ногу с 2 байтами за 3 такта, поэтому должны быть почти нулевые промахи I-cache L1.

В вашем xor с / без теста REX пропускная способность основной памяти, возможно, была узким местом, если вы протестировали с достаточно большим циклом, чтобы не вписываться в кеш-память L3. Я потребляю 4 * 2B за такт на процессоре с частотой 3 ГГц, что составляет примерно максимум 25 ГБ / с DDR3-1600MHz. Тем не менее, даже кеш L3 будет достаточно быстрым, чтобы идти в ногу с 4 * 3B за цикл.

Интересно, что основная память BW является узким местом; Первоначально я предполагал, что декодирование (в блоках по 16 байт) станет узким местом для 3-байтовых XOR, но я думаю, что они достаточно малы.


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

Для бенчмаркинга в тактовых циклах используйте perf stat ./a.out . Существуют и другие полезные счетчики производительности, которые необходимы для понимания характеристик производительности.

См. X86-64 Относительная производительность jmp для результатов перфорирования от Core2 (8 циклов на jmp) и некоторая неизвестная микроархитектура, где она составляет ~ 10c на jmp.


Детали современных характеристик производительности процессора достаточно трудно понять даже при более или менее условиях белого ящика (чтение руководства по оптимизации Intel и то, что они опубликовали относительно внутренних компонентов ЦП). Вы собираетесь застрять рано и часто, если будете настаивать на тестировании черного ящика, где вы не читаете такие вещи, как статьи arstechnica о новом дизайне процессора, или, может быть, некоторые более подробные материалы, такие как обзор микроархива Хасуэлла Дэвида Кэнтера или аналогичный Запись Sandybridge я связал ранее.

Если вы застряли рано и часто в порядке, и вы весело проводите время, то непременно продолжайте делать то, что делаете. Но это затрудняет людям отвечать на ваши вопросы, если вы не знаете этих деталей, как в этом случае. : / например, моя первая версия этого ответа предполагала, что вы достаточно читали, чтобы узнать, что такое uop cache.

  • Определить версию сборки (CLR) сборки
  • как загрузить все сборки из вашего каталога / bin
  • Код C ++ для проверки гипотезы Collatz быстрее, чем assembly вручную - почему?
  • Как загрузить сборку в AppDomain со всеми ссылками рекурсивно?
  • Безопасно ли читать конец конца буфера на одной странице на x86 и x64?
  • Как извлечь сборку из GAC?
  • C #: зачем подписывать сборку?
  • Как точно работает инструкция x86 LOOP?
  • .NET Assembly Diff / Compare Tool - Что доступно?
  • Размер сборки .NET влияет на производительность?
  • Как загрузить сборку .NET для операций отражения и впоследствии выгрузить ее?
  • Interesting Posts

    Android – NullPointerException в SearchView в панели действий

    Получение идентификатора streamа из streamа

    Почему мы не можем сделать CON, PRN, Null папку в окнах?

    MVVM в WPF – Как предупредить ViewModel об изменениях в Model … или я должен?

    Почему определения указателей функций работают с любым количеством амперсандов ‘&’ или звездочек ‘*’?

    How To: Лучший способ рисовать таблицу в консольном приложении (C #)

    NDK: как включить * .so файлы в AndroidStudio

    Android Studio TransformException: ошибка: выполнение не выполнено для задачи ‘: app: transformClassesWithDexForDebug’

    Почему Excel 2010 автоматически переформатирует номера?

    Mac – резервное копирование только определенной папки с использованием машины времени

    Использование app.config в .Net Core

    Окно 8: использование ОЗУ (память)

    Событие Click не работает с динамически сгенерированными элементами

    ggplot2: изменение порядка стеков на гистограмме

    Изменение цвета горизонтальной полосы хода Android

    Давайте будем гением компьютера.