Сколько циклов процессора требуется для каждой инструкции сборки?

Я слышал, что есть онлайн-книга 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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

http://www.agner.org/optimize

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


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

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

Ответ 4

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

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

Временные данные

Сначала вам нужны фактические тайминги. Они отличаются архитектурой процессора, а лучшим ресурсом является Agner Fog таблицы инструкций. Эти таблицы содержат не менее тридцати разных микроархитексов, которые представляют собой минимальное/типичное время, которое команда берет с входных данных, готовых к выпуску. В словах Agner:

Задержка:. Это задержка, которую команда генерирует в цепь зависимостей. Номера - это минимальные значения. Кэш пропускает, несоосность и исключения могут увеличить количество часов значительно. Если включена гиперпоточность, использование одного и того же исполнительные устройства в другом потоке приводят к снижению производительности. Денормальные числа, 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 выполнят только один цикл. Например, если инструкции добавления были не зависимыми, возможно, что на современных чипах все 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

Agner предоставляет метрику, которая захватывает некоторый из этого потенциала parallelism, называемый обратной пропускной способностью:

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

Для add это указано как 0.25, что означает, что до 4 add могут выполняться каждый цикл (давая обратную пропускную способность 1 / 4 = 0.25).

Число обратных пропускных способностей также дает подсказку о возможности конвейерной обработки инструкции. Например, на самых последних чипах x86 общие формы команды imul имеют задержку в 3 цикла, и внутри нее может обрабатываться только один исполнительный блок (в отличие от add, который обычно имеет четыре блока с возможностью добавления). Тем не менее, наблюдаемая пропускная способность для длинной серии независимых инструкций imul составляет 1/цикл, а не 1 каждые 3 такта, как вы могли ожидать, учитывая задержку в 3 раза. Причина в том, что блок imul конвейерный: он может запускать новый imul каждый цикл, даже если предыдущее умножение не завершено.

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

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

Подробный анализ

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

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

Покрытие всех деталей увеличило бы размер этого уже долгого ответа в 10 или более раз, поэтому я просто укажу вам на лучшие ресурсы. У Agner Fog есть Optimizing Asembly guide, в котором подробно описывается точный анализ цикла с помощью дюжины инструкций. См. " 12.7 Пример анализа узких мест в векторных циклах", который начинается на странице 95 в текущей версии PDF.

Основная идея заключается в том, что вы создаете таблицу с одной строкой на каждую команду и отмечаете используемые ресурсы выполнения. Это позволяет увидеть узкие места пропускной способности. Кроме того, вам необходимо изучить цикл для переносимых зависимостей, чтобы убедиться, что ограничение ограничено пропускной способностью (см. " 12.16 Анализ зависимостей" для сложного случая).

Если вы не хотите делать это вручную, Intel выпустила Intel Architecture Code Analyzer, который является инструментом, который автоматизирует этот анализ. В настоящее время он не обновляется за пределами Skylake, но результаты по-прежнему в значительной степени разумны для Kaby Lake, поскольку микроархитектура не сильно изменилась, и поэтому тайминги остаются сопоставимыми. Этот ответ содержит много подробностей и предоставляет пример вывода, а руководство пользователя не является плохим (хотя он устарел по отношению к новейшим версиям).

Другие источники

Agner обычно предоставляет тайминги для новых архитектур вскоре после их выпуска, но вы также можете проверить instlatx64 для аналогично организованных таймингов в результаты InstLatX86 и InstLatX64. Результаты охватывают множество интересных старых фишек, и новые фишки обычно появляются довольно быстро. Результаты в основном согласуются с Agner's, за некоторыми исключениями здесь и там. Вы также можете найти латентность памяти и другие значения на этой странице.

Вы даже можете получить результаты синхронизации непосредственно от Intel в Руководство по оптимизации IA32 и Intel 64 в Приложение C: ПОКАЗАТЕЛЬ ИНСТРУКЦИИ И ЧЕРЕЗ. Лично я предпочитаю версию Agner, потому что они более полные, часто прибывают до обновления руководства Intel, и их проще использовать, поскольку они предоставляют электронную таблицу и PDF-версию.

Наконец, x86 тег wiki имеет множество ресурсов для оптимизации x86, включая ссылки на другие примеры того, как делать точный анализ циклов кода.

Ответ 5

lock xchg eax, dword ptr [edx]

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

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

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