Получать и добавлять с использованием атомарных операций OpenMP - PullRequest
8 голосов
/ 27 октября 2010

Я использую OpenMP и мне нужно использовать операцию извлечения и добавления. Однако OpenMP не предоставляет соответствующую директиву / вызов. Я хотел бы сохранить максимальную переносимость, поэтому я не хочу полагаться на встроенные функции компилятора.

Скорее, я ищу способ использовать атомарные операции OpenMP для реализации этого, но я зашел в тупик. Можно ли это сделать? N.B., следующий код почти делает то, что я хочу:

#pragma omp atomic
x += a

Почти - но не совсем, поскольку мне действительно нужно старое значение x. fetch_and_add должен быть определен для получения того же результата, что и следующий (только без блокировки):

template <typename T>
T fetch_and_add(volatile T& value, T increment) {
    T old;
    #pragma omp critical
    {
        old = value;
        value += increment;
    }
    return old;
}

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

Ответы [ 2 ]

4 голосов
/ 27 октября 2011

Начиная с openmp 3.1 есть поддержка захвата атомарных обновлений, вы можете захватить либо старое, либо новое значение.Поскольку мы должны в любом случае вывести значение из памяти, чтобы увеличить его, имеет смысл только получить к нему доступ, скажем, к регистру ЦП и поместить его в переменную с приватным потоком.

Естьхороший обходной путь, если вы используете gcc (или g ++), посмотрите атомарные встроенные функции: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

Кажется, что компилятор Intel C / C ++ также поддерживает это, но я не пробовал.

На данный момент (до реализации openmp 3.1) я использовал встроенные функции-оболочки в C ++, где вы можете выбрать, какую версию использовать во время компиляции:

template <class T>
inline T my_fetch_add(T *ptr, T val) {
  #ifdef GCC_EXTENSION
  return __sync_fetch_and_add(ptr, val);
  #endif
  #ifdef OPENMP_3_1
  T t;
  #pragma omp atomic capture
  { t = *ptr; *ptr += val; }
  return t;
  #endif
}

Обновление: я простопробовал компилятор Intel C ++, в настоящее время он поддерживает openmp 3.1 (реализован атомарный захват).Intel предлагает бесплатное использование своих компиляторов в Linux для некоммерческих целей:

http://software.intel.com/en-us/articles/non-commercial-software-download/

GCC 4.7 будет поддерживать openmp 3.1, когда он в конечном итоге будет выпущен ... надеюсь, скоро:)

2 голосов
/ 17 ноября 2010

Если вы хотите получить старое значение x и значение a не изменилось, используйте (xa) в качестве старого значения:

fetch_and_add(int *x, int a) {
 #pragma omp atomic
 *x += a;

 return (*x-a);
}

ОБНОВЛЕНИЕ: на самом деле это не был ответ, потому что x можно изменить послеатомный другой поток.Таким образом, кажется невозможным сделать универсальное «извлечение и добавление» с помощью OMP Pragmas.Под универсальной я подразумеваю операцию, которую можно легко использовать из любого места кода OMP.

Вы можете использовать omp_*_lock функции для имитации атомики:

typedef struct {omp_lock_t lock;int value;} atomic_simulated_t;

fetch_and_add(atomic_simulated_t *x, int a)
{
  int ret;
  omp_set_lock(x->lock);
  x->value +=a;
  ret = x->value;
  omp_unset_lock(x->lock);
}

Это уродливо и медленно (выполнение 2 атомных операций вместо 1).Но если вы хотите, чтобы ваш код был очень переносимым, он будет не самым быстрым во всех случаях.

Вы говорите «как следующее (только без блокировки)».Но в чем разница между «неблокирующими» операциями (с использованием префикса «LOCK» процессора, или LL / SC или т. Д.) И операциями блокировки (которые реализуются с помощью нескольких атомарных инструкций, цикла занятости для короткого ожидания разблокировки и спящего режима ОСдолго ждет)?

...