Инструкция по сборке для замены openmp критической области
У меня есть массив элементов, которые обрабатываются задачами openmp. Вполне возможно, что задача может добавить новые элементы в конце массива. Конечно, эти элементы также должны быть обработаны и могут создавать новые предметы. В настоящее время я использую этот код
int p;
#pragma omp critical
{
p=l.n++;
}
Это просто резервирует место в конце массива. Тип l
является
struct list
{
int n;
double *e;
}
а также p
будет использоваться как индекс для хранения нового элемента. Мне было интересно, если есть способ выполнить эту операцию без использования критической области. Существует ли инструкция по сборке, которая копирует значение, а затем атомарно увеличивает оригинал?
Код будет выполняться на процессоре Nehalem, не нужно беспокоиться о старых машинах
3 ответа
#pragma omp atomic capture
p = l.n++;
При захвате значения следует использовать атомарный инкремент, если оборудование его поддерживает.
Узнайте больше о #pragma omp atomic
в этом вопросе: openMP, atomic vs критический?
А вот документация Intel для#pragma omp atomic
,
Я попытался составить минимальный пример с gcc -fopenmp -m32 -O2 -S
:
int i, j;
void foo (void)
{
#pragma omp atomic capture
i = j++;
}
То, что я получил, было простым атомарным "выборкой и добавлением", чего мы и хотим:
movl $1, %eax # eax = 1
lock xaddl %eax, j # atomic {swap (eax,j); j = eax + j;}
movl %eax, i # i = eax
ret
Да, на x86 есть несколько возможных вариантов.
XADD r/m, r
Эта инструкция атомарно добавляет второй операнд (r) к первому операнду (r/m) и загружает второй операнд (r) с исходным значением первого операнда (r/m).
Чтобы использовать его, вам нужно загрузить второй операнд с величиной приращения (я думаю, 1, здесь), а первый операнд должен быть местом в памяти того, что увеличивается.
Этой инструкции должен предшествовать префикс LOCK (он сделает ее атомарной).
InterlockedAdd()
функция в Microsoft Visual C++ делает это и, AFAIR, использует XADD
если это доступно (доступно с i80486).
Другой способ заключается в использовании цикла с CMPXCHG
инструкция...
псевдокод:
while (true)
{
int oldValue = l.n;
int newValue = oldValue + 1;
if (CAS(&l.n, newValue, oldValue) == oldValue)
break;
}
CAS()
, что означает Compare And Swap
(общий термин в параллельном программировании) - это функция, которая пытается атомарно заменить значение в памяти новым значением. Замена успешна, когда заменяемое значение равно последнему предоставленному параметру, oldValue
, Иначе не получается. CAS
возвращает исходное значение из памяти, что позволяет узнать, была ли замена успешной (мы сравниваем возвращенное значение с oldValue
). Ошибка (возвращаемое исходное значение отличается от oldValue
) указывает, что между чтением oldValue
и в тот момент, когда мы попытались заменить его newValue
другой поток изменил значение в памяти. В этом случае мы просто повторяем всю процедуру.
CMPXCHG
инструкция х86 CAS
,
В Microsoft Visual C++ InterlockedCompareExchange()
использования CMPXCHG
реализовать CAS
,
Если XADD
не доступно, InterlockedAdd()
реализуется с использованием CAS
/CMPXCHG
/InterlockedCompareExchange()
,
На некоторых других процессорах могут быть другие возможности. Некоторые допускают атомарное выполнение нескольких смежных инструкций.
На самом деле это просто атомарное приращение, которое возвращает результат, который выглядит следующим образом:
mov p, 1 ; p must be a register
lock xadd [l.n], p
И теперь ты знаешь. Однако я не вижу смысла использовать это на практике, но есть способы сделать это, не прибегая к ассемблерному коду.