Действительно ли атомарные функции делают переменные переменными в CUDA?
Я написал очень простой код, запрашивающий поток 0 обновить глобальную переменную, в то время как другие потоки продолжают читать эту переменную. Но я обнаружил, что другие потоки действительно не получают значение.
Код здесь, это довольно просто. Кто-нибудь может дать мне какое-нибудь предложение, как это исправить? большое спасибо
__global__ void addKernel(int *c)
{
int i = threadIdx.x;
int j = 0;
if (i == 0)
{
while(*c < 2000){
int temp = *c;
printf("*c = %d\n",*c);
atomicCAS(c,temp, temp+1);
}
}else{
while(*c < 1000)
{
j++;
}
}
}
1 ответ
Я хотел бы привести аналогию: представьте на секунду, что атомарные операции являются мьютексами: чтобы программа была четко определена, два потока, обращающиеся к общему ресурсу, должны оба согласиться использовать мьютекс для доступа к ресурсу исключительно. Если один из потоков обращается к ресурсу без предварительного удержания мьютекса, результат не определен.
То же самое относится и к атомарному элементу: если вы решите трактовать определенное местоположение в памяти как атомарную переменную, то все потоки, обращающиеся к этому местоположению, должны согласиться и рассматривать это как таковое, чтобы ваша программа имела значение. Вы должны манипулировать им только через атомарные нагрузки и хранилища, а не через комбинацию неатомарных и атомарных операций.
Другими словами, это:
atomicCAS(c,temp, temp+1);
Содержит атомную загрузку-сравнение-магазин. Полученная инструкция будет загружена в глобальную память c
Выполните сравнение и пройдите до глобальной памяти, чтобы сохранить новое значение.
Но это:
while(*c < 2000)
Не является атомным в любом случае. Компилятор (и аппаратное обеспечение) понятия не имеет, что c
возможно, был изменен другим потоком. Таким образом, вместо того, чтобы перейти к глобальной памяти, он будет просто читать из самого быстрого доступного кеша. Возможно, компилятор даже поместит переменную в регистр, потому что он не увидит, чтобы кто-то еще модифицировал ее в текущем потоке.
То, что вы хотели бы, это что-то вроде (воображаемое):
while (atomicLoad(c) < 2000)
Но, насколько мне известно, на момент написания этой статьи в CUDA такой конструкции не было.
В связи с этим volatile
Может помочь квалификатор: он говорит компилятору не оптимизировать переменную и считает ее "модифицируемой из внешних источников". Это вызовет загрузку для каждого чтения переменной, хотя я не уверен, что эта загрузка обходит все кэши. На практике это может сработать, но в теории я не думаю, что вы должны полагаться на это. Кроме того, это также отключит любую оптимизацию для этой переменной (например, постоянное распространение или продвижение переменной в регистр для лучшей производительности).
Вы можете попробовать следующий хак (я не пробовал):
while(atomicAdd(c, 0) < 2000)
Это выдаст атомарную инструкцию, которая загружается из глобальной памяти, и поэтому должна увидеть самое последнее значение c
, Тем не менее, он также вводит (бесполезный в этом случае) атомарный магазин.