спин-блокировка x86 с использованием cmpxchg - PullRequest
14 голосов
/ 04 августа 2011

Я новичок в использовании встроенной сборки gcc, и мне было интересно, можно ли реализовать на многоядерном компьютере x86 спин-блокировку (без условий гонки) как (с использованием синтаксиса AT & T):

spin_lock:
mov 0 eax
lock cmpxchg 1 [lock_addr]
jnz spin_lock
ret

spin_unlock:
lock mov 0 [lock_addr]
ret

Ответы [ 2 ]

24 голосов
/ 04 августа 2011

У вас правильная идея, но ваш асм не работает:

cmpxchg не может работать с непосредственным операндом, только регистры.

lock не является допустимым префиксомдля mov.mov для выровненного адреса является атомарным в x86, так что вам все равно не нужно lock.

Прошло некоторое время с тех пор, как я использовал синтаксис AT & T, надеюсь, я все запомнил:

spin_lock:
    xorl   %ecx, %ecx
    incl   %ecx            # newVal = 1
spin_lock_retry:
    xorl   %eax, %eax      # expected = 0
    lock; cmpxchgl %ecx, (lock_addr)
    jnz    spin_lock_retry
    ret

spin_unlock:
    movl   $0,  (lock_addr)    # atomic release-store
    ret

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

void spin_lock(int *p)
{
    while(!__sync_bool_compare_and_swap(p, 0, 1));
}

void spin_unlock(int volatile *p)
{
    asm volatile ("":::"memory"); // acts as a memory barrier.
    *p = 0;
}

Как говорит Бо ниже, закрытые инструкции требуют затрат: каждаявы должны получить эксклюзивный доступ к строке кэша и заблокировать ее, пока lock cmpxchg запускает , как для обычного хранения этой строки кэша, но удерживается в течение выполнения lock cmpxchg.Это может задержать разблокирующий поток, особенно если несколько потоков ожидают блокировки.Даже без большого количества процессоров все еще легко и стоит оптимизировать:

void spin_lock(int volatile *p)
{
    while(!__sync_bool_compare_and_swap(p, 0, 1))
    {
        // spin read-only until a cmpxchg might succeed
        while(*p) _mm_pause();  // or maybe do{}while(*p) to pause first
    }
}

Инструкция pause жизненно важна для производительности на процессорах HyperThreading, когда у вас есть код, который вращается вот так - он позволяетвторой поток выполняется, пока первый поток вращается.На процессорах, которые не поддерживают pause, он обрабатывается как nop.

pause, также предотвращает ошибочные спекуляции с порядком памяти при выходе из спин-цикла, когда наконец настало время сделатьснова настоящая работа. Какова цель инструкции «PAUSE» в x86?

Обратите внимание, что спин-блокировки на самом деле используются редко: обычно используется что-то вроде критической секции или futex.Они включают в себя спин-блокировку для повышения производительности при низкой конкуренции, но затем возвращаются к механизму сна и оповещения с помощью ОС.Они также могут принимать меры для улучшения справедливости и многих других вещей, которые не делает цикл cmpxchg / pause.


Также обратите внимание, что cmpxchg не требуется для простого спин-блокировки:Вы можете использовать xchg и затем проверить, было ли старое значение 0 или нет.Выполнение меньшего количества работы внутри инструкции lock может держать линию кэша закрепленной в течение меньшего времени.См. Блокировка вокруг манипуляций с памятью через встроенную сборку для полной реализации asm с использованием xchg и pause (но все еще без возврата к спящему с ОС, просто вращается бесконечно.)

2 голосов
/ 17 октября 2012

Это приведет к меньшему количеству конфликтов на шине памяти:

void spin_lock(int *p)
{
    while(!__sync_bool_compare_and_swap(p, 0, 1)) while(*p);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...