C ++ amp — Как синхронизировать потоки друг с другом в C ++ AMP?

Я пытаюсь использовать C ++ AMP для вычисления Фибоначчи, и каждое число будет зависеть от двух предыдущих. поэтому код такой:

for (int i = 0; i < size; i++){
a[i] = 0;
if (i == 0 || i == 1) a[i] = 1;
}
array_view<int, 1> A(size, a);
parallel_for_each(
A.extent,
[=](index<1> idx)restrict(amp){
if( A[idx] == 0 ){
while (A[idx - 2] == 0);
while (A[idx - 1] == 0);
A[idx] = A[idx - 1] + A[idx - 2];
}
});

третий поток (idx [0] == 2) будет ждать в этой строке:

A[idx] = A[idx - 1] + A[idx - 2];

для него не будет установлено ненулевое число, поэтому все последующие потоки будут просто застрять в цикле.

есть ли выход?

1

Решение

Как отмечали вышеупомянутые комментаторы, Фибоначчи — один из худших возможных кандидатов на распараллеливание на GPU. Лучшее, на что вы можете надеяться, это использовать примитивы синхронизации для выполнения одной операции за другой и сериализации всех потоков!

Среда выполнения C ++ AMP не дает никаких гарантий относительно порядка планирования листов потоков на графическом процессоре. Предположим, он решает запланировать тайлы, вычисляющие верхнюю половину серии в первую очередь? Нижние никогда не будут работать, а ядро ​​зависнет. Там нет эквивалента wait() в C ++ AMP, поэтому поток (ы) не будут блокироваться, не давая среде выполнения никаких указаний на то, что он должен поменять блокированные потоки / тайлы и запускать другие. Вы по существу используете while в качестве замены wait, Это плохая идея для процессора или графического процессора, поскольку вы вводите состояние гонки, когда память читается и записывается одновременно.

Тем не менее, ваш вопрос является правильным в контексте общей синхронизации потоков. С C ++ AMP у вас в основном есть два механизма; барьеры и атомные операции.

В следующем примере показано использование атома для подсчета числа случайных чисел в theData массив, который больше, чем 0,999. Массив инициализируется случайными числами от 0,0 до 1,0, так что это число довольно мало. Это означает, что затраты на блокировку атомарных операций возникают нечасто.

array_view<float, 1> theDataView(int(theData.size()), theData);
int exceptionalOccurrences = 0;
array_view<int> count(1, &exceptionalOccurrences);
parallel_for_each(theDataView.extent, [=] (index<1> idx) restrict(amp)
{
if (theDataView[idx] >= 0.999f) // Exceptional occurrence.
{
atomic_fetch_inc(&count(0));
}
theDataView[idx] = // Update the value...
});
count.synchronize();

Хотя это показывает, как использовать атомарные, это более эффективное решение этой проблемы с использованием операции сокращения (map и), чтобы полностью избежать использования атомных функций.

В следующем примере используется барьер для синхронизации обращений к памяти между потоками в плитке. Первая половина кода перед барьером использует все потоки для загрузки данных в память tile_static перед использованием барьера, заставляющего все потоки ждать, пока все данные не загрузятся, прежде чем получить доступ к данным.

array<float, 2> inData(1000, 1000);
array<float, 2> outData(1000, 1000);

parallel_for_each(view,
inData.extent.tile<tileSize, tileSize>(), [=, &inData, &outData]
(tiled_index<tileSize, tileSize> tidx) restrict(amp)
{
tile_static float localData[tileSize][tileSize];
localData[tidx.local[1]][tidx.local[0]] = inData[tidx.global];

tidx.barrier.wait();
index<2> outIdx(index<2>(tidx.tile_origin[1],
tidx.tile_origin[0]) + tidx.local);
outData[outIdx] = localData[tidx.local[0]][tidx.local[1]];
});

Таким образом, атомы дают вам возможность безопасно делить память между всеми потоками за счет значительных накладных расходов на синхронизацию. Атомика не блокирует. Барьеры позволяют синхронизировать выполнение и доступ к памяти между потоками внутри плитки, которые они блокируют. В C ++ AMP нет функции, которая объединяет эти две функции, вы не можете блокировать все потоки на GPU.

Это фундаментальное предположение модели программирования. Все потоки подвергаются минимальной синхронизации и могут работать с небольшими или нулевыми зависимостями между потоками. Большая часть этого освещена в главах по оптимизации и производительности в моем C ++ AMP book.

2

Другие решения

Других решений пока нет …