Ближайшие соседи в 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, вы должны рассмотреть, потому что таким образом распараллеливание в основном эффективно; работающие блоки обработки используются (меньше потоков холостого хода).