Вопрос

Какова стоимость атомарной операции (любой из операций сравнения и подкачки или атомарного добавления / уменьшения)?Сколько циклов он потребляет?Будет ли это приостанавливать работу других процессоров на SMP или NUMA или заблокирует доступ к памяти?Будет ли это сбрасывать буфер изменения порядка в вышедшем из строя процессоре?

Какие последствия будут иметь для кэша?

Меня интересуют современные, популярные процессоры:x86, x86_64, PowerPC, SPARC, Itanium.

Это было полезно?

Решение

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

Стоимость префикса БЛОКИРОВКИ x86, или CAS, до PentiumPro (как описано в документе), - это доступ к памяти (например, пропуск кэша), + остановка операций с памятью другими процессорами, + любые конфликты с другими процессорами, пытающимися ЗАБЛОКИРОВАТЬ шину.Однако, начиная с PentiumPro, для обратной записи (т.е.кэшируемая) память (вся память, с которой работает приложение, если только вы не общаетесь напрямую с оборудованием), вместо блокировки всех операций с памятью блокируется только соответствующая строка кэша (на основе ссылки, размещенной выше).

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

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

Он объясняет, что загрузка + CAS в одном местоположении может фактически стоить двух промахов кэша, например, связанных с загрузкой / условных хранилищ (см. Последнее).Его объяснение опирается на знание Протокол согласованности кэша MESI.Он использует 4 состояния для cacheline:M (odified), E (исключительный), S (сохраненный), I (nvalid) (и, следовательно, это называется MESI), объяснено ниже, где это необходимо.Объясненный сценарий заключается в следующем:

  • загрузка приводит к пропуску кэша - соответствующая строка кэша загружается из памяти в разделяемом состоянии (т.е.другим процессорам по-прежнему разрешено хранить эту строку кэша в памяти;в этом состоянии никакие изменения не допускаются).Если местоположение находится в памяти, этот промах в кэше пропускается. Возможная стоимость:1 промах в кэше. (пропускается, если cacheline находится в общем, эксклюзивном или Измененном состоянии, т.е.данные находятся в кэше L1 этого процессора).
  • программа вычисляет новые значения для сохранения,
  • и он запускает атомарную инструкцию CAS.
    • Он должен избегать одновременной модификации, поэтому он должен удалить копии строки кэша из кэша других процессоров, чтобы перевести строку кэша в эксклюзивное состояние. Возможная стоимость:1 промах в кэше. Это не требуется, если он уже находится в исключительном владении, т.е.в Исключительном или Измененном состоянии.В обоих состояниях ни один другой процессор не поддерживает cacheline, но в эксклюзивном состоянии он не был изменен (пока).
    • После этого обмена данными переменная изменяется в локальном кэше нашего процессора, после чего она становится глобально видимой для всех других процессоров (поскольку их кэши согласованы с нашими).В конечном итоге он будет записан в основную память в соответствии с обычными алгоритмами.
    • Другие процессоры, пытающиеся прочитать или изменить эту переменную, сначала должны будут получить эту строку кэша в общем или эксклюзивном режиме, и для этого свяжутся с этим процессором и получат обновленную версию строки кэша.Вместо этого заблокированная операция может стоить только потери кэша (поскольку строка кэша будет запрошена непосредственно в эксклюзивном состоянии).

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

Другие советы

Я выполнил некоторое профилирование со следующей настройкой:Тестовая машина (AMD Athlon64 x2 3800+) была загружена, переключена в длительный режим (прерывания отключены), и интересующая инструкция была выполнена в цикле, развернуто 100 итераций и 1000 циклов цикла.Тело цикла было выровнено по 16 байтам.Время было измерено с помощью команды rdtsc до и после цикла.Дополнительно был выполнен фиктивный цикл без какой-либо инструкции (который измерял 2 цикла на итерацию цикла и 14 циклов для остальных), и результат был вычтен из результата времени профилирования команды.

Были измерены следующие инструкции:

  • "lock cmpxchg [rsp - 8], rdx" (как при сопоставлении, так и при несоответствии),
  • "lock xadd [rsp - 8], rdx",
  • "lock bts qword ptr [rsp - 8], 1"

Во всех случаях измеренное время составляло около 310 циклов, погрешность составляла около +/- 8 циклов

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

Чтобы оценить стоимость заблокированной инструкции при пропуске кэша, я добавил wbinvld инструкция перед заблокированной инструкцией и поместите wbinvld плюс еще один add [rsp - 8], rax в цикл сравнения.В обоих случаях стоимость составляла около 80 000 циклов на пару команд!В случае блокировки bts разница во времени составляла около 180 циклов на команду.

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

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

Для загрузки компьютера я использовал 64-разрядную версию FreeLdr из проекта ReactOS.Вот исходный код asm:

#define LOOP_COUNT 1000
#define UNROLLED_COUNT 100

PUBLIC ProfileDummy
ProfileDummy:

    cli

    // Get current TSC value into r8
    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax

    mov rcx, LOOP_COUNT
    jmp looper1

.align 16
looper1:

REPEAT UNROLLED_COUNT
    // nothing, or add something to compare against
ENDR

    dec rcx
    jnz looper1

    // Put new TSC minus old TSC into rax
    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret

PUBLIC ProfileFunction
ProfileFunction:

    cli

    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax
    mov rcx, LOOP_COUNT

    jmp looper2

.align 16
looper2:

REPEAT UNROLLED_COUNT
    // Put here the code you want to profile
    // make sure it doesn't mess up non-volatiles or r8
    lock bts qword ptr [rsp - 8], 1
ENDR

    dec rcx
    jnz looper2

    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret

На базе шины SMP, атомарный префикс LOCK утверждает ли (включает) сигнал проводной шины LOCK#.Это запретит другим процессорам / устройствам на шине использовать его.

Книга Ppro и P2 http://books.google.com/books?id=3gDmyIYvFH4C&pg=PA245&dq=lock+instruction+pentium&lr=&ei=_E61S5ehLI78zQSzrqwI&cd=1#v=onepage&q=lock%20instruction%20pentium&f=false страницы 244-246

Заблокированные инструкции - это сериализация, синхронизация операции ..../о вышедшем из строя / заблокированном RMW/read-modify-write = сама атомарная / инструкция гарантирует, что процессор выполнит все инструкции перед заблокированной инструкцией, прежде чем выполнить ее./о еще не сброшенных записях / это заставляет все размещенные записи внутри процессора сбрасываться во внешнюю память перед выполнением следующей инструкции.

/о SMP/ семафор находится в кэше в состоянии S...выдача транзакции чтения и аннулирования для 0 байт даты (это уничтожение / общих копий строки кэша в соседних процессорах /)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top