Просто удар ...
Я подозреваю, что транспонирование ваших циклов может помочь.
for (n=0; n<NgS; n++) {
for (m=0; m<Nobs; m++) {
ngo=rInd[m]-1;
Aggregation[idx(n,ngo,NgO)] += precalculatedValue(m,n);
}
}
Причина, по которой я это сделал, заключается в том, что idx
меняется быстрее с ngo
(функция m
), чем с n
, поэтому создание внутреннего цикла улучшает согласованность.Заметьте, я также сделал precalculatedValue функцией (m, n), потому что вы сказали, что это так - это делает псевдокод более понятным.
Затем вы можете начать с оставления внешнего цикла на хосте и созданияядро для внутреннего цикла (64,480-параллельного параллелизма достаточно для заполнения большинства современных графических процессоров).
Во внутреннем цикле просто начните с использования atomicAdd () для обработки коллизий.Если они нечасты, производительность графических процессоров Fermi не должна быть слишком плохой.В любом случае вы будете ограничены пропускной способностью, поскольку арифметическая интенсивность этого вычисления мала.Так что, как только это сработает, измерьте полосу пропускания, которую вы достигаете, и сравните с пиком для вашего GPU.Если вы далеко, , тогда подумайте о дальнейшей оптимизации (возможно, путем распараллеливания внешнего цикла - одна итерация на блок потока и выполните внутренний цикл с использованием некоторой оптимизации совместной памяти и взаимодействия потоков).
Ключ: начать с простого, измерить производительность, а затем решить, как оптимизировать.
Обратите внимание, что этот расчет выглядит очень похоже на расчет гистограммы, который имеет аналогичные проблемы, поэтому вы можете захотеть использовать Google для GPUгистограммы, чтобы увидеть, как они были реализованы.
Одна идея состоит в том, чтобы отсортировать (rInd[m]
, m
) пары, используя thrust::sort_by_key()
, а затем (так как дубликаты rInd
будут сгруппированы вместе), выможете перебирать их и делать сокращения без коллизий.(Это один из способов создания гистограмм.) Вы могли бы сделать это даже с thrust::reduce_by_key()
.