Почему memory_order_relaxed использует атомарные (с префиксом) инструкции на x86?

На Visual C ++ 2013, когда я компилирую следующий код

#include <atomic>

int main()
{
std::atomic<int> v(2);
return v.fetch_add(1, std::memory_order_relaxed);
}

Я получаю обратно следующую сборку на x86:

51               push        ecx
B8 02 00 00 00   mov         eax,2
8D 0C 24         lea         ecx,[esp]
87 01            xchg        eax,dword ptr [ecx]
B8 01 00 00 00   mov         eax,1
F0 0F C1 01      lock xadd   dword ptr [ecx],eax
59               pop         ecx
C3               ret

и аналогично на x64:

B8 02 00 00 00    mov         eax,2
87 44 24 08       xchg        eax,dword ptr [rsp+8]
B8 01 00 00 00    mov         eax,1
F0 0F C1 44 24 08 lock xadd   dword ptr [rsp+8],eax
C3                ret

Я просто не понимаю: почему расслабленный приращение int переменная требует lock префикс?

Есть ли причина для этого, или они просто не включают оптимизацию удаления?


* Я использовал /O2 с /NoDefaultLib урезать его и избавиться от ненужного кода времени выполнения C, но это не имеет отношения к вопросу.

5

Решение

Поскольку блокировка все еще требуется для того, чтобы она была атомарной; даже с memory_order_relaxed требование увеличения / уменьшения слишком строго, чтобы быть без блокировки.

Вообразите то же самое без замков.

v = 0;

И затем мы создаем 100 потоков, каждый с этой командой:

v++;

А потом вы ждете завершения всех потоков, чего бы вы ожидали? К сожалению, это может быть и не 100. Скажем, значение v = 23 загружается одним потоком, и до создания 24 другой поток также загружает 23, а затем записывает 24 тоже. Таким образом, потоки фактически отрицают друг друга. Это потому, что сам прирост не атомный. Конечно, загрузка, сохранение, добавление могут быть атомарными сами по себе, но приращение состоит из нескольких шагов, поэтому оно не является атомарным.

Но с std :: atomic все операции являются атомарными, независимо от std::memory_order установка. Вопрос только в том, в каком порядке они произойдут. memory_order_relaxed все еще гарантирует атомарность, это могло бы просто быть не в порядке относительно всего, что происходит рядом с ним, даже работая с тем же значением.

4

Другие решения

Сначала для справки рассмотрим нормальное назначение. Он генерирует следующее на Intel / 64:

// v = 10;
000000014000E0D0  mov         eax,0Ah
000000014000E0D5  xchg        eax,dword ptr [v (014001BCDCh)]

Затем рассмотрите непринужденное задание:

// v.store(10, std::memory_order_relaxed);
000000014000E0D0  mov         dword ptr [v (014001BCDCh)],0Ah

Сейчас, std::atomic::fetch_add() является операцией чтения-изменения-записи, и нет смысла делать это «грязным» способом. По умолчанию вы получаете std::memory_order_seq_cst согласно http://en.cppreference.com/w/cpp/atomic/atomic/fetch_add. Так что, я думаю, имеет смысл сгенерировать одну нативную инструкцию для этого. По крайней мере, на Intel / 64, где это дешево:

// v.fetch_add(1, std::memory_order_relaxed)
000000014000E0D0  mov         eax,1
000000014000E0D5  lock xadd   dword ptr [v (014001BCDCh)],eax

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

// auto x = v.load(std::memory_order_relaxed);
000000014000E0D0  mov         eax,dword ptr [v (014001BCDCh)]

// ++x;
000000014000E0D6  inc         eax

//v.store(x, std::memory_order_relaxed);
000000014000E0D8  mov         dword ptr [v (014001BCDCh)],eax
-1

По вопросам рекламы [email protected]