Странное поведение с использованием локальной памяти в OpenCL
В настоящее время я работаю над проектом, подающим в суд на OpenCL на NVIDIA Tesla C1060 (версия драйвера 195.17). Однако у меня странное поведение, которое я не могу объяснить. Вот код, который озадачивает меня (сокращен для ясности и в целях тестирования):
kernel void TestKernel(global const int* groupOffsets, global float* result,
local int* tmpData, const int itemcount)
{
unsigned int groupid = get_group_id(0);
unsigned int globalsize = get_global_size(0);
unsigned int groupcount = get_num_groups(0);
for(unsigned int id = get_global_id(0); id < itemcount; id += globalsize, groupid += groupcount)
{
barrier(CLK_LOCAL_MEM_FENCE);
if(get_local_id(0) == 0)
tmpData[0] = groupOffsets[groupid];
barrier(CLK_LOCAL_MEM_FENCE);
int offset = tmpData[0];
result[id] = (float) offset;
}
}
Этот код должен загрузить смещение для каждой рабочей группы в локальную память, а затем прочитать его обратно и записать в соответствующую запись выходного вектора. Для большинства рабочих элементов это работает, но для каждой рабочей группы рабочие элементы с локальными идентификаторами с 1 по 31 читают неправильное значение. Мой выходной вектор (для рабочей группы =128) выглядит следующим образом:
index 0: 0
index 1- 31: 470400
index 32-127: 0
index 128: 640
index 129-159: 471040
index 160-255: 640
index 256: 1280
index 257-287: 471680
index 288-511: 1280
...
результат, который я ожидал, будет
index 0-127: 0
index 128-255: 640
index 256-511: 1280
...
Странно то, что проблема возникает только тогда, когда я использую меньше рабочих элементов itemcount (поэтому он работает, как и ожидалось, когда globalsize>=itemcount, то есть каждый рабочий элемент обрабатывает только одну запись). Я предполагаю, что это как-то связано с циклом. Кто-нибудь знает, что я делаю не так и как это исправить?
Обновление: я узнал, что, кажется, работает, если я изменяю
if(get_local_id(0) == 0)
tmpData[0] = groupOffsets[groupid];
в
if(get_local_id(0) < 32)
tmpData[0] = groupOffsets[groupid];
Это удивляет меня еще больше, поэтому, хотя это может решить проблему, я не чувствую себя комфортно, решая ее таким образом (поскольку это может сломать в другой раз). Кроме того, я бы предпочел не терять производительность при работе на оборудовании класса Geforce 8xxx из-за дополнительных (не связанных с этим оборудованием, насколько я понимаю) обращений к памяти. Так что вопрос до сих пор остается.
1 ответ
Во-первых, и это важно, вы должны быть осторожны, что itemcount
является кратным локального размера работы, чтобы избежать расхождения при выполнении барьера.
Все рабочие элементы в рабочей группе, выполняющей ядро на процессоре, должны выполнить эту функцию, прежде чем любой из них сможет продолжить выполнение за барьером. Эта функция должна встречаться всеми рабочими элементами в рабочей группе, выполняющей ядро.
Вы можете реализовать это следующим образом:
unsigned int itemcountrounded = get_local_size(0) * ((itemcount + get_local_size(0) - 1) / get_local_size(0));
for(unsigned int id = get_global_id(0); id < itemcountrounded; id += globalsize, groupid += groupcount)
{
// ...
if (id < itemcount)
result[id] = (float) offset;
}
Вы сказали, что код был сокращен для простоты, что произойдет, если вы запустите то, что вы опубликовали? Просто интересно, нужно ли вам ставить барьер и для глобальной памяти.