Современные процессоры - это сложные звери, использующие конвейерную обработку , суперскалярное выполнение и выполнение не по порядку и другие методы, которые затрудняют анализ производительности. . но не невозможно !
Хотя вы больше не можете просто складывать задержки потока инструкций для получения общего времени выполнения, вы все равно можете получить (часто) очень точный анализ поведения некоторого фрагмента кода (особенно цикла), как описано ниже и в других связанных ресурсах.
Время выполнения инструкции
Во-первых, вам нужны реальные сроки. Они различаются в зависимости от архитектуры ЦП, но лучшим ресурсом для таймингов x86 в настоящее время являются таблицы инструкций Agner Fog . Охватывающие не менее тридцать различных микроархитектур, в этих таблицах перечислены команды latency , которые представляют собой минимальное / типичное время, которое команда берет из входов, готовых к выводу, доступных. По словам Агнера:
Задержка: Это задержка, которую инструкция генерирует в
цепочка зависимостей. Числа являются минимальными значениями. Кеш пропускает,
смещение, и исключения могут увеличить счетчик часов
значительно. Там, где включена гиперпоточность, использование того же
Выполнение блоков в другом потоке приводит к снижению производительности.
Денормальные числа, NAN и бесконечность не увеличивают время ожидания.
единица времени, используемая для тактовых циклов ядра, а не эталонных
определяется счетчиком меток времени.
Так, например, инструкция add
имеет задержку в один цикл, так что последовательность зависимых инструкций добавления, как показано, будет иметь задержку 1 цикл на add
:
add eax, eax
add eax, eax
add eax, eax
add eax, eax # total latency of 4 cycles for these 4 adds
Обратите внимание, что это не означает, что add
инструкции будут занимать только 1 цикл каждый. Например, если инструкции добавления были не зависимыми, возможно, что на современных чипах все 4 инструкции добавления могут выполняться независимо в одном и том же цикле:
add eax, eax
add ebx, ebx
add ecx, ecx
add edx, edx # these 4 instructions might all execute, in parallel in a single cycle
Агнер предоставляет метрику, которая отражает часть этого потенциального параллелизма, называемую обратной пропускной способностью :
Взаимная пропускная способность: Среднее количество тактов ядра на одну инструкцию для серии независимых инструкций одного вида
в той же теме.
Для add
это указано как 0.25
, означающее, что до 4 add
инструкций может выполняться каждый цикл (давая обратную пропускную способность 1 / 4 = 0.25
).
Номер обратной пропускной способности также дает подсказку о возможности конвейерной обработки команды . Например, в большинстве последних чипов x86 обычные формы инструкции imul
имеют задержку в 3 цикла, и внутренне только один исполняющий модуль может обрабатывать их (в отличие от add
, который обычно имеет четыре добавляемых модуля). Тем не менее, наблюдаемая пропускная способность для длинной серии независимых imul
инструкций составляет 1 / цикл, а не 1 каждые 3 цикла, как можно было бы ожидать, учитывая задержку 3. Причина в том, что блок imul
конвейеризован: он может start new imul
каждый цикл , даже если предыдущее умножение еще не завершено.
Это означает, что серия независимых imul
инструкций может выполняться со скоростью до 1 за цикл, но серия зависимых imul
инструкций будет выполняться только по 1 каждые 3 циклы (поскольку следующие imul
не могут начаться, пока не будет готов результат предыдущего).
Итак, с помощью этой информации вы можете начать понимать, как анализировать время выполнения команд на современных процессорах.
Детальный анализ
Тем не менее, выше только царапины на поверхности. Теперь у вас есть несколько способов просмотра последовательности инструкций (задержка или пропускная способность), и может быть неясно, какой из них использовать.
Кроме того, существуют другие ограничения, не охваченные вышеуказанными числами, такие как тот факт, что определенные команды конкурируют за одни и те же ресурсы в ЦП, и ограничения в других частях конвейера ЦП (такие как декодирование команд), которые могут привести к при более низкой общей пропускной способности, чем вы рассчитываете, просто взглянув на задержку и пропускную способность. Помимо этого, у вас есть факторы «за пределами ALU», такие как доступ к памяти и прогнозирование ветвлений: целые темы сами по себе - вы можете в основном моделировать их хорошо, но это требует работы. Например, вот недавнее сообщение , в котором в ответе подробно рассматриваются большинство важных факторов.
Покрытие всех деталей увеличило бы размер этого длинного ответа в 10 и более раз, поэтому я просто укажу вам лучшие ресурсы. Agner Fog имеет направляющую Optimizing Asembly , которая подробно описывает точный анализ цикла с дюжиной или около того инструкций. См. « 12.7 Пример анализа узких мест в векторных циклах», который начинается на стр. 95 в текущей версии PDF.
Основная идея состоит в том, что вы создаете таблицу с одной строкой на инструкцию и отмечаете ресурсы выполнения, которые использует каждый. Это позволяет увидеть любые узкие места в пропускной способности. Кроме того, вам нужно проверить цикл на наличие переносимых зависимостей, чтобы выяснить, ограничивает ли какая-либо из них пропускную способность (см. « 12.16 Анализ зависимостей» для сложного случая).
Если вы не хотите делать это вручную, Intel выпустила Intel Architecture Code Analyzer , который является инструментом, который автоматизирует этот анализ. В настоящее время он не обновлялся после Skylake, но результаты для Kaby Lake все еще в значительной степени приемлемы, поскольку микроархитектура не сильно изменилась, и поэтому время остается сопоставимым. Этот ответ содержит много подробностей и предоставляет пример выходных данных, а руководство пользователя не так уж и плохо (хотя оно устарело в отношении новейших версий).
Другие источники
Agner обычно предоставляет синхронизацию для новых архитектур вскоре после их выпуска, но вы также можете проверить instlatx64 для аналогично организованных синхронизаций в результатах InstLatX86
и InstLatX64
. Результаты охватывают множество интересных старых фишек, и новые фишки обычно появляются довольно быстро. Результаты в основном согласуются с результатами Агнера, за некоторыми исключениями здесь и там. Вы также можете найти задержку памяти и другие значения на этой странице.
Вы даже можете получить результаты синхронизации напрямую от Intel в их Руководстве по оптимизации IA32 и Intel 64 в Приложение C. ИНСТРУКЦИЯ ПО ПРОШЕДШЕМУ ВРЕМЕНИ . Лично я предпочитаю версию Агнера, потому что они более полные, часто приходят до обновления руководства Intel, и их легче использовать, поскольку они предоставляют электронную таблицу и PDF-версию.
Наконец, вики-тег x86 обладает множеством ресурсов по оптимизации x86, включая ссылки на другие примеры того, как выполнить точный цикл анализа последовательностей кода.
Если вы хотите глубже изучить тип «анализа потоков данных», описанный выше, я бы порекомендовал Вихрь Введение в графы потоков данных .