Инструкция по сборке для замены 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

И теперь ты знаешь. Однако я не вижу смысла использовать это на практике, но есть способы сделать это, не прибегая к ассемблерному коду.

Другие вопросы по тегам