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