Барьеры, кажется, синхронизируются только в пределах временного окна - PullRequest
0 голосов
/ 13 марта 2019

В настоящее время я пытался реализовать метод FDTD для решения уравнений Максвелла с использованием OpenCL.Алгоритм довольно прост, вычислить текущее h-поле из старого электрического поля и рассчитать текущее e-поле из текущего h-поля.Чем начать следующую итерацию.В OpenCL возникают проблемы с синхронизацией, поскольку электронное поле не может быть рассчитано до того, как будет вычислено h-поле, и следующая итерация должна начаться синхронно.Для этого должна использоваться только одна рабочая группа.Я гарантирую это, сделав глобальное и локальное рабочее пространство одинакового размера.В моем gpu максимальное значение рабочего элемента 256, это означает, что для трехмерного проблемного пространства у меня может быть 6 рабочих элементов в каждом измерении.Вот почему каждый рабочий элемент вычисляет куб значений полей.Проблема, с которой я сталкиваюсь, состоит в том, что алгоритм дает правильные результаты, поскольку каждый рабочий элемент вычисляет менее 12 значений полей в каждом измерении, всего 1728 значений полей.Если рабочим элементам приходится вычислять больше значений полей, результаты алгоритма становятся все хуже и хуже.Я также протестировал алгоритм с плавающей запятой двойной точности, который дал еще худшие результаты.Мое подозрение, гарантированное результатами двойной точности, состоит в том, что для вычисления всех значений поля требуется много времени, и синхронизация больше не работает должным образом.Вот исходный код ядра OpenCL:

    kernel void fdtd3d_noiter_fp32(
                   global float3* old_e,
                   global float3* old_h,
                   global float3* current_e,
                   global float3* current_h,
                   global float3* e_curl_integral,
                   global float3* e_field_integral,
                   global float3* h_curl_integral,
                   global float3* h_field_integral,
                   constant float4* ex_factor,
                   constant float4* ey_factor,
                   constant float4* ez_factor,
                   constant float4* hx_factor,
                   constant float4* hy_factor,
                   constant float4* hz_factor,
                   constant char* geometry,
                   float grid_width_x,
                   float grid_width_y,
                   float grid_width_z,
                   int uwidth,
                   int udepth,
                   float max_time,
                   float time_step,
                   int batch_size_x,
                   int batch_size_y,
                   int batch_size_z,
                   constant char* source_factor,
                   float source_amplitude,
                   float source_ramp_len,
                   float source_frequency)
{
// get the actual id
    const int  ID = get_global_id(0) * batch_size_x + get_global_id(1) * batch_size_y * uwidth + get_global_id(2) * batch_size_z * uwidth * udepth + 1 + uwidth + uwidth * udepth;</p>

<code>int id = ID;

for (float current_time = 0; current_time < max_time; current_time += time_step)
{
    for (int z = 0; z < batch_size_z; z++)
    {
        for (int y = 0; y < batch_size_y; y++)
        {
            for (int x = 0; x < batch_size_x; x++)
            {
                id = ID + x + y * uwidth + z * uwidth * udepth;
                // calculate the current magnetic field from the old electric field
                float hx_curl_term = ((old_e[id + uwidth].z - old_e[id].z) / grid_width_y) - ((old_e[id + uwidth * udepth].y - old_e[id].y) / grid_width_z);
                float hy_curl_term = ((old_e[id + uwidth * udepth].x - old_e[id].x) / grid_width_z) - ((old_e[id + 1].z - old_e[id].z) / grid_width_x);
                float hz_curl_term = ((old_e[id + 1].y - old_e[id].y) / grid_width_x) - ((old_e[id + uwidth].x - old_e[id].x) / grid_width_y);
                h_curl_integral[id].x += hx_curl_term;
                h_curl_integral[id].y += hy_curl_term;
                h_curl_integral[id].z += hz_curl_term;
                h_field_integral[id] += old_h[id];
                current_h[id].x = + hx_factor[id].x * old_h[id].x - hx_factor[id].y * hx_curl_term
                         - hx_factor[id].z * h_curl_integral[id].x - hx_factor[id].w * h_field_integral[id].x;
                current_h[id].y = + hy_factor[id].x * old_h[id].y - hy_factor[id].y * hy_curl_term
                         - hy_factor[id].z * h_curl_integral[id].y - hy_factor[id].w * h_field_integral[id].y;
                current_h[id].z = + hz_factor[id].x * old_h[id].z - hz_factor[id].y * hz_curl_term
                         - hz_factor[id].z * h_curl_integral[id].z - hz_factor[id].w * h_field_integral[id].z;
            }
        }
    }

    barrier(CLK_LOCAL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);

    // calculate the current electric field from the current magnetic field
    for (int z = 0; z < batch_size_z; z++)
    {
        for (int y = 0; y < batch_size_y; y++)
        {
            for (int x = 0; x < batch_size_x; x++)
            {
                id = ID + x + y * uwidth + z * uwidth * udepth;
                float ex_curl_term = ((current_h[id].z - current_h[id - uwidth].z) / grid_width_y) - ((current_h[id].y - current_h[id - uwidth * udepth].y) / grid_width_z);
                float ey_curl_term = ((current_h[id].x - current_h[id - uwidth * udepth].x) / grid_width_z) - ((current_h[id].z - current_h[id - 1].z) / grid_width_x);
                float ez_curl_term = ((current_h[id].y - current_h[id - 1].y) / grid_width_x) - ((current_h[id].x - current_h[id - uwidth].x) / grid_width_y);
                e_curl_integral[id].x += ex_curl_term;
                e_curl_integral[id].y += ey_curl_term;
                e_curl_integral[id].z += ez_curl_term;
                e_field_integral[id] += old_e[id];
                current_e[id].x = ex_factor[id].x * old_e[id].x - ex_factor[id].y * e_field_integral[id].x
                         + ex_factor[id].z * ex_curl_term + ex_factor[id].w * e_curl_integral[id].x;
                current_e[id].y = ey_factor[id].x * old_e[id].y - ey_factor[id].y * e_field_integral[id].y
                         + ey_factor[id].z * ey_curl_term + ey_factor[id].w * e_curl_integral[id].y;
                current_e[id].z = ez_factor[id].x * old_e[id].z - ez_factor[id].y * e_field_integral[id].z
                         + ez_factor[id].z * ez_curl_term + ez_factor[id].w * e_curl_integral[id].z;

                // zero ez out if the soure_factor is 1
                current_e[id].z -= abs(source_factor[id]) * current_e[id].z;
                // than add the source. this way a hard source is created!
                current_e[id].z += source_factor[id] * source_amplitude * ramped_sin_fp32(current_time, source_ramp_len, source_frequency);
                current_e[id] *= geometry[id];
            }
        }
    }

    // synchronize the workitems after calculating the current fields
    barrier(CLK_LOCAL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);

    // now memory consistency is assured and the fields can be copied
    for (int z = 0; z < batch_size_z; z++)
    {
        for (int y = 0; y < batch_size_y; y++)
        {
            for (int x = 0; x < batch_size_x; x++)
            {
                id = ID + x + y * uwidth + z * uwidth * udepth;
                old_h[id] = current_h[id];
                old_e[id] = current_e[id];
            }
        }
    }
    barrier(CLK_LOCAL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);
}
</code>

}

Я надеюсь, что код не является неразборчивым, и кто-то может мне помочь!

lyding

1 Ответ

0 голосов
/ 16 марта 2019

Давайте начнем с небольших вещей:

барьер (CLK_LOCAL_MEM_FENCE

Использование локального барьера не имеет смысла, если вы не используете локальные переменные.

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

синхронизация в графическом процессоре выполняетсяаппаратное обеспечение, и хотя это возможно, это маловероятная проблема.

Теперь большая проблема:

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

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

Я не внимательно изучил ваш код, но возможно (AFAICT) вам не нужно использовать одну рабочую группу.То, как я написал бы это (псевдокод):

OpenCL код:

kernel1(arguments) { calculate the current magnetic field from the old electric field }
kernel2(arguments) { calculate the current electric field from the current magnetic field }
kernel3(arguments) { copy current to old (both e/h) }

Хост-код:

for (float current_time = 0; current_time < max_time; current_time += time_step)
   {
       clEnqueueNDRangeKernel(queue, kernel1, ...);
       clEnqueueNDRangeKernel(queue, kernel2, ...);
       clEnqueueNDRangeKernel(queue, kernel3, ...);
   }

Является ли это хорошей идеей, зависит.на многих вещах - размер проблемы, временной шаг и т. д. Но это позволит вам использовать трехмерную сетку для глобальной работы, и графический процессор будет выполнять их параллельно.Возможно, попробуйте найти какой-нибудь код OpenCL или CUDA на github, который вычисляет уравнения Максвелла.

...