Ближайшие соседи в CUDA Particles
Edit 2: пожалуйста, взгляните на этого пересечения сообщений для TLDR.
редактировать: учитывая, что частицы сегментированы в ячейки сетки (скажем 16^3
grid), лучше ли позволить запускать одну рабочую группу для каждой ячейки сетки и столько рабочих элементов в одной рабочей группе, сколько может быть максимального количества частиц в ячейке сетки?
в этом случае я мог бы загрузить все частицы из соседних ячеек в локальной памяти и перебирайте их, вычисляя некоторые свойства. Затем я мог бы записать определенное значение в каждую частицу в текущей ячейке сетки.
будет ли этот подход полезен для запуска ядра для всех частиц и для каждой итерации (большую часть времени одних и тех же) соседей?
кроме того, каково идеальное соотношение number of particles/number of grid cells
?
я пытаюсь переопределить (изменить) частицы CUDA для OpenCL и используйте его для запроса ближайшего соседей для каждой частицы. Я создал следующие структуры:
- буфер
P
удержание 3D-позиций всех частиц (float3
) буфер
Sp
хранениеint2
пары идентификаторов частиц и их пространственные хэши.Sp
отсортирована в соответствии с хэш. (Хэш - это просто простое линейное отображение из 3D в 1D-пока нет Z-индексирования.)-
буфер
L
хранениеint2
пары начальных и конечных позиций определенных пространственных хэшей в bufferSp
. Пример:L[12] = (int2)(0, 50)
.-
L[12].x
индекс (вSp
) из первый частица с пространственным хэшем12
. -
L[12].y
индекс (вSp
) из последние частица с пространственным хэшем12
.
-
теперь, когда у меня есть все эти буферы, я хочу перебрать все частицы в P
и для каждой частицы перебирать через ближайших соседей. В настоящее время у меня есть ядро, которое выглядит так (псевдокод):
__kernel process_particles(float3* P, int2* Sp, int2* L, int* Out) {
size_t gid = get_global_id(0);
float3 curr_particle = P[gid];
int processed_value = 0;
for(int x=-1; x<=1; x++)
for(int y=-1; y<=1; y++)
for(int z=-1; z<=1; z++) {
float3 neigh_position = curr_particle + (float3)(x,y,z)*GRID_CELL_SIDE;
// ugly boundary checking
if ( dot(neigh_position<0, (float3)(1)) +
dot(neigh_position>BOUNDARY, (float3)(1)) != 0)
continue;
int neigh_hash = spatial_hash( neigh_position );
int2 particles_range = L[ neigh_hash ];
for(int p=particles_range.x; p<particles_range.y; p++)
processed_value += heavy_computation( P[ Sp[p].y ] );
}
Out[gid] = processed_value;
}
проблема с этим кодом заключается в том, что он медленный. Я подозреваю нелинейный доступ к памяти GPU (в частности P[Sp[p].y]
во внутреннем-самое for
loop), чтобы вызвать медлительность.
то, что я хочу сделать, это использовать кривая Z-порядка как пространственный хэш. Таким образом, я мог иметь только 1 for
цикл итерации через непрерывный диапазон памяти при запросе соседи. Единственная проблема в том, что я не знаю, какими должны быть значения z-индекса start и stop.
Святой Грааль я хочу добиться:
__kernel process_particles(float3* P, int2* Sp, int2* L, int* Out) {
size_t gid = get_global_id(0);
float3 curr_particle = P[gid];
int processed_value = 0;
// How to accomplish this??
// `get_neighbors_range()` returns start and end Z-index values
// representing the start and end near neighbors cells range
int2 nearest_neighboring_cells_range = get_neighbors_range(curr_particle);
int first_particle_id = L[ nearest_neighboring_cells_range.x ].x;
int last_particle_id = L[ nearest_neighboring_cells_range.y ].y;
for(int p=first_particle_id; p<=last_particle_id; p++) {
processed_value += heavy_computation( P[ Sp[p].y ] );
}
Out[gid] = processed_value;
}
1 ответов
вы должны внимательно изучить алгоритмы кода Мортона. Обнаружение столкновений в реальном времени Ericsons объясняет это очень хорошо.
Ericson-обнаружение столкновений в реальном времени
вот еще одно хорошее объяснение, включая некоторые тесты:
кодирование/декодирование Мортона через битное чередование: реализации
алгоритмы Z-Order определяют только пути координат, в которых вы можете хэшировать из 2 или 3D координат просто целое число. Хотя алгоритм идет глубже для каждой итерации, вы должны сами установить ограничения. Обычно стоп-индекс обозначается часовым. Если часовой остановится,вы узнаете, на каком уровне находится частица. Таким образом, максимальный уровень, который вы хотите определить, покажет вам количество ячеек в измерении. Например, при максимальном уровне в 6 у вас есть 2^6 = 64. У вас будет 64x64x64 ячейки в вашей системе (3D). Это также означает, что вы должны использовать целочисленные координаты. Если вы используете поплавки, вы должны конвертировать как coord.x = 64*float_x
и так далее.
Если вы знаете, сколько клеток у вас есть в вашей системе, вы можете определить свои пределы. Вы пытаетесь использовать двоичный octree?
поскольку частицы находятся в движении (в этом примере CUDA), вы должны попытаться распараллелить количество частиц вместо ячеек.
Если вы хотите построить списки ближайших соседей, вы должны сопоставить частицы с ячейками. Это делается через таблицу, которая сортируется затем клетками к частицам. Тем не менее, вы должны перебирать частицы и обращаться к их соседям.
о коде:
проблема с этим кодом заключается в том, что он медленный. Я подозреваю нелинейный доступ к памяти GPU (в частности, P[Sp[p].y] во внутреннем-наиболее для цикла), чтобы вызвать медлительность.
Помните Дональда Кнута. Вы должны измерить, где горлышко бутылки. Вы можете использовать профилировщик NVCC и искать узкое место. Не уверен, что OpenCL имеет как профилировщик.
// ugly boundary checking if ( dot(neigh_position<0, (float3)(1)) + dot(neigh_position>BOUNDARY, (float3)(1)) != 0) continue;
Я думаю, что вы не должны разветвлять его таким образом, как насчет возврата нуля, когда вы вызываете heavy_computation
. Не уверен, но, возможно, у вас есть своего рода предсказание здесь филиал. Попробуйте снять что-то.
параллельный запуск по ячейкам-хорошая идея, только если у вас нет доступа для записи к данным частиц, иначе вам придется использовать atomics. Если вы перейдете через диапазон частиц, вместо этого вы прочитаете доступ к ячейкам и но вы создаете свою сумму параллельно, и вас не принуждают к какой-то расовой парадигме.
кроме того, каково идеальное соотношение количества частиц/количества ячеек сетки?
действительно зависит от ваших алгоритмов и упаковки частиц в вашем домене, но в вашем случае я бы определил размер ячейки, эквивалентный диаметру частицы, и просто использовал количество ячеек, которые вы получаете.
поэтому, если вы хотите использовать Z-порядок и достичь ваш Святой Грааль, попробуйте использовать целочисленные координаты и их хэш.
также попробуйте использовать большее количество частиц. Около 65000 частиц, таких как примеры CUDA, вы должны рассмотреть, потому что таким образом распараллеливание в основном эффективно; работающие блоки обработки используются (меньше потоков холостого хода).