Сколько циклов ЦП необходимо для каждой инструкции по сборке? - PullRequest
47 голосов
/ 28 марта 2009

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

Вот пример, в приведенном ниже коде mov / lock равен 1 циклу ЦП, а xchg - 3 цикла ЦП.

// This part is Platform dependent!
#ifdef WIN32
inline int CPP_SpinLock::TestAndSet(int* pTargetAddress, 
                                              int nValue)
{
    __asm
    {
        mov edx, dword ptr [pTargetAddress]
        mov eax, nValue
        lock xchg eax, dword ptr [edx]
    }
    // mov = 1 CPU cycle
    // lock = 1 CPU cycle
    // xchg = 3 CPU cycles
}

#endif // WIN32

Кстати: вот URL кода, который я разместил: http://www.codeproject.com/KB/threads/spinlocks.aspx

Ответы [ 5 ]

30 голосов
/ 28 марта 2009

Учитывая конвейеризацию, обработку заказов, микрокод, многоядерные процессоры и т. Д., Нет никакой гарантии, что конкретный раздел кода сборки займет ровно x циклов ЦП / тактовый цикл / любые циклы.

Если такая ссылка существует, она сможет предоставить только широкие обобщения для конкретной архитектуры, и в зависимости от того, как реализован микрокод, вы можете обнаружить, что Pentium M отличается от Core 2 Duo, который отличается от Двухъядерный AMD и др.

Обратите внимание, что эта статья была обновлена ​​в 2000 году и написана ранее. Даже Pentium 4 сложно определить с точки зрения синхронизации команд - PIII, PII и оригинальный Pentium были проще, и ссылки на тексты, вероятно, основывались на тех более ранних процессорах, которые имели более четко определенную синхронизацию команд.

В наши дни люди обычно используют статистический анализ для оценки времени кода.

21 голосов
/ 28 марта 2009

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

Точные задержки для процессоров Intel и AMD приведены в таблицах инструкций Agner Fog . См. Также Справочное руководство по оптимизации архитектур Intel® 64 и IA-32 и Задержки инструкций и пропускная способность для процессоров AMD и Intel x86 (из недавно удаленного ответа Can Berk Güder, содержащего только ссылки) , AMD также имеет на своем веб-сайте руководства в формате pdf со своими официальными ценностями.

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

Конечно, это все еще требует, чтобы вы знали много других деталей о ЦП, таких как, насколько глубоко он конвейерен, сколько инструкций он может выдать за цикл, количество исполнительных блоков и так далее. И, конечно, эти цифры различаются для разных процессоров. Но вы часто можете прийти к разумному среднему значению, которое более или менее работает для всех процессоров.

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

Редактировать Посмотрите в руководстве по оптимизации Intel, таблица C-13: Первый столбец - это тип инструкции, затем для каждого CPUID есть число столбцов для задержки. CPUID указывает, к какому семейству процессоров применяются числа, и они описаны в других местах документа. Задержка определяет, сколько циклов требуется для того, чтобы получить доступ к результату инструкции, поэтому это число, которое вы ищете.

Столбцы пропускной способности показывают, сколько команд этого типа можно выполнить за цикл.

Посмотрев xchg в этой таблице, мы увидим, что в зависимости от семейства процессоров требуется 1-3 цикла, а mov - 0,5-1. Они предназначены для форм в регистр-регистр инструкций, а не для lock xchg с памятью, которая намного медленнее. И что еще более важно, очень переменная задержка и влияние на окружающий код (гораздо медленнее, когда есть конфликт с другим ядром), поэтому смотреть только в лучшем случае - ошибка. (Я не посмотрел, что означает каждый CPUID, но я полагаю, что .5 предназначены для Pentium 4, который запускал некоторые компоненты чипа с двойной скоростью, позволяя ему делать вещи в половинных циклах)

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

18 голосов
/ 08 июля 2017

Современные процессоры - это сложные звери, использующие конвейерную обработку , суперскалярное выполнение и выполнение не по порядку и другие методы, которые затрудняют анализ производительности. . но не невозможно !

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

Время выполнения инструкции

Во-первых, вам нужны реальные сроки. Они различаются в зависимости от архитектуры ЦП, но лучшим ресурсом для таймингов 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, включая ссылки на другие примеры того, как выполнить точный цикл анализа последовательностей кода.

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

13 голосов
/ 28 марта 2009

Измерение и подсчет циклов ЦП больше не имеет смысла для x86.

Прежде всего, спросите себя, на какой процессор вы рассчитываете циклы? Core-2? Атлон? Pentium-M? Атом? Все эти процессоры выполняют код x86, но все они имеют разное время выполнения. Выполнение даже варьируется между различными степпингами одного и того же процессора.

Последним x86, в котором подсчет циклов имел смысл, был Pentium-Pro.

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

Таким образом, время для инструкции зависит не только от самой инструкции, но также и от окружающего кода.

В любом случае: Вы можете оценить использование ресурсов пропускной способности и задержку инструкций для разных процессоров. Соответствующую информацию можно найти на сайтах Intel и AMD.

Агнер Фог имеет очень хорошее резюме на своем веб-сайте. См. Таблицы инструкций по задержке, пропускной способности и количеству операций. См. Документ микроархитектуры в формате PDF, чтобы узнать, как их интерпретировать.

http://www.agner.org/optimize

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


Кстати, поскольку ваш пример кода является базовым строительным блоком структуры данных без блокировки: рассматривали ли вы возможность использования встроенных функций компилятора? На win32 вы можете включить intrin.h и использовать такие функции, как _InterlockedExchange.

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

6 голосов
/ 04 января 2010

lock xchg eax, pword ptr [edx]

Обратите внимание, что блокировка блокирует память для извлечения памяти для всех ядер, это может занять 100 циклов на некоторых многоядерных системах, и также необходимо очистить строку кэша. Это также остановит трубопровод. Так что я не буду беспокоиться об остальном.

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

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

...