CUDA-OpenCL CPU 4x быстрее, чем OpenCL или версия CUDA GPU

симулятор волны, над которым я работал с C# + Cudafy (C# -> CUDA или OpenCL переводчик) отлично работает, за исключением того, что запуск версии процессора OpenCL (драйвер Intel, 15 " MacBook Pro Retina i7 2.7 GHz, GeForce 650M (Kepler, 384 ядра)) примерно в четыре раза быстрее, чем версия GPU.

(это происходит, использую ли я CL или CUDA GPU бэкэнд. Версии OpenCL GPU и CUDA работают почти одинаково.)

уточнить, для образца проблема:

  • процессор OpenCL 1200 Гц
  • OpenCL GPU 320 Гц
  • CUDA GPU - ~330 Гц

Я не могу объяснить, почему версия CPU будет быстрее чем GPU. В этом случае код ядра, который выполняется (в случае CL) на CPU и GPU, идентичен. Я выбираю либо CPU, либо GPU-устройство во время инициализации, но помимо этого все идентичный.

редактировать

вот код C#, который запускает одно из ядер. (Остальные очень похожи.)

    public override void UpdateEz(Source source, float Time, float ca, float cb)
    {
        var blockSize = new dim3(1);
        var gridSize = new dim3(_gpuEz.Field.GetLength(0),_gpuEz.Field.GetLength(1));

        Gpu.Launch(gridSize, blockSize)
            .CudaUpdateEz(
                Time
                , ca
                , cb
                , source.Position.X
                , source.Position.Y
                , source.Value
                , _gpuHx.Field
                , _gpuHy.Field
                , _gpuEz.Field
            );

    }

и вот соответствующая функция ядра CUDA, сгенерированная Cudafy:

extern "C" __global__ void CudaUpdateEz(float time, float ca, float cb, int sourceX, int sourceY, float sourceValue,  float* hx, int hxLen0, int hxLen1,  float* hy, int hyLen0, int hyLen1,  float* ez, int ezLen0, int ezLen1)
{
    int x = blockIdx.x;
    int y = blockIdx.y;
    if (x > 0 && x < ezLen0 - 1 && y > 0 && y < ezLen1 - 1)
    {
        ez[(x) * ezLen1 + ( y)] = ca * ez[(x) * ezLen1 + ( y)] + cb * (hy[(x) * hyLen1 + ( y)] - hy[(x - 1) * hyLen1 + ( y)]) - cb * (hx[(x) * hxLen1 + ( y)] - hx[(x) * hxLen1 + ( y - 1)]);
    }
    if (x == sourceX && y == sourceY)
    {
        ez[(x) * ezLen1 + ( y)] += sourceValue;
    }
}

просто для полноты, вот C#, который используется для генерации CUDA:

    [Cudafy]
    public static void CudaUpdateEz(
        GThread thread
        , float time
        , float ca
        , float cb
        , int sourceX
        , int sourceY
        , float sourceValue
        , float[,] hx
        , float[,] hy
        , float[,] ez
        )
    {
        var i = thread.blockIdx.x;
        var j = thread.blockIdx.y;

        if (i > 0 && i < ez.GetLength(0) - 1 && j > 0 && j < ez.GetLength(1) - 1)
            ez[i, j] =
                ca * ez[i, j]
                +
                cb * (hy[i, j] - hy[i - 1, j])
                -
                cb * (hx[i, j] - hx[i, j - 1])
                ;

        if (i == sourceX && j == sourceY)
            ez[i, j] += sourceValue;
    }

очевидно, что if в этом ядре плохо, но даже результирующий срыв трубопровода не должен вызывать такой крайности производительность delta.

единственное, что еще выскакивает на меня, это то, что я использую хромую схему распределения сетки/блока - т. е. сетка-это размер массива, который нужно обновить, и каждый блок-это один поток. Я уверен, что это оказывает некоторое влияние на производительность, но я не вижу, чтобы это было 1/4 скорости кода CL, работающего на CPU. Аргх!

1 ответов


отвечая на это, чтобы получить его из списка без ответа.

опубликованный код указывает, что запуск ядра задает threadblock из 1 (активного) потока. Это не способ написать быстрый код GPU, так как он оставит большую часть возможностей GPU в режиме ожидания.

типичные размеры threadblock должны быть по крайней мере 128 потоков на блок, и выше часто лучше, в кратных 32, до предела 512 или 1024 на блок, в зависимости от GPU.

GPU " нравится" чтобы скрыть задержку, имея много параллельной работы "доступно". Указание большего количества потоков на блок помогает в достижении этой цели. (Имея достаточно большое количество параметра в сетке также может помочь.)

кроме того, GPU выполняет потоки в группах по 32. Указание только 1 потока на блок или не кратного 32 оставит несколько слотов выполнения в каждом выполняемом блоке threadblock. 1 поток на блок особенно плох.