Я пытаюсь реализовать глобальную синхронизацию в OpenCL 1.2 с использованием атомарности, и мне интересно, есть ли какой-либо способ гарантировать, что операции чтения из разных рабочих групп (которые, по логике программы, очевидно, происходят после атомарного приращения) видят обновленный результат.
Подробности: У меня есть двоичное дерево (хранящееся в виде массива) значений, которые я хочу вычислить. Значения внутренних узлов зависят от значений, рассчитанных для их дочерних узлов. Имеется n листьев и создается n потоков, которые проходят от листа -> корень.
Для любого конкретного значения:
Предположим, есть два потока, не обязательно в одной рабочей группе:
Таким образом, потоку, обрабатывающему любой узел, гарантируется, что оба его потомка уже обработаны в «реальном времени». У меня есть рабочая реализация той же идеи, работающая с пулом потоков на ЦП.
Проблема:
Когда я читаю вычисленные значения для дочерних элементов из внутреннего узла, меньшинство из них устаревшие/неинициализированные. Фактически, даже соответствующий счетчик для дочерних элементов (который модифицируется только атомарными операциями) может давать устаревшие значения (например, 0 или 1, а не 2). Из описанной выше процедуры невозможно обработать узел, не посетив каждый из его дочерних узлов дважды.
Это подсказывает мне, что OpenCL 1.2 имеет только гарантии конечной согласованности при чтении глобальной памяти между рабочими группами в одной и той же памяти, даже если записи являются атомарными. Верна ли моя оценка (и если да, то есть ли способы обойти это)?
Я очень ценю любую помощь в этом вопросе!
ТЛ;ДР: Я знаю, что OpenCL 1.2, вообще говоря, имеет расслабленную модель памяти согласованности - применимо ли это также к атомарной записи? (И если да, то можно ли гарантировать, что операции чтения, происходящие после атомарной записи, не устарели?)
(Если это невозможно, то это может показаться мне серьезной проблемой, поскольку атомарные приращения, которые включают в себя чтение, наверняка могут синхронизироваться с предыдущими атомарными операциями записи (иначе многие гарантии корректности будут утеряны)? Конечно, это невозможно. операции чтения также могут синхронизироваться между рабочими группами с помощью атомарной записи?)
@Tim Спасибо за ваши комментарии. Да, в этом контексте я могу гарантировать, что программа завершится корректно, даже если мы не можем принять порядок рабочих элементов и рабочих групп. В конечном итоге я решил проблему, реализовав собственный атомный_load (который, как ни странно, отсутствует во встроенных функциях).
В итоге я решил это сам. Для всех, у кого есть подобная проблема, похоже, что атомарные операции не синхронизируются с обычным чтением, а Atomic_load не предоставляется API OpenCL. Я реализовал это сам (который синхронизируется с записью из разных рабочих групп и рабочих элементов) следующим образом:
uint atomic_load(volatile global uint* ptr) {
return atomic_cmpxchg(ptr, DUMMY_VALUE, DUMMY_VALUE);
}
Где DUMMY_VALUE — это просто любое значение (единственная важная часть — то, что последние два аргумента имеют одно и то же значение).
Это работает, поскольку если *ptr == DUMMY_VALUE, DUMMY_VALUE сохраняется и также возвращается, а если они не равны, старое значение *ptr сохраняется и также возвращается.
Другой вариант — использовать atomic_or(..,0)
или atomic_add(..., 0)
(сложение целого числа) или что-то подобное. Это может сэкономить вам около часа общей задержки сообщения, поскольку на некотором оборудовании, поскольку сравнение и замена могут иметь большую задержку канала на один или два часа, но это, вероятно, не будет иметь измеримого значения. Рад, что вы решили свою проблему.
Это, безусловно, более разумные реализации; Я предположил, не проверяя, что эти функции только устанавливают значения, а не возвращают их. Еще раз спасибо :)
Я думаю, вам может понадобиться конкретный MRE, чтобы помочь нам помочь вам здесь. Обычно ограничения памяти используются для наложения ограничений порядка и видимости даже при использовании атомов. Однако помните, что на самом деле мы не можем предположить, когда на машине выполняются разные рабочие элементы и рабочие группы. Они не могут работать одновременно.