2013-05-07 3 views
6

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

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

Чтобы уточнить, для задачи. Пример:

  • OpenCL CPU 1200 Гц
  • OpenCL GPU 320 Гц
  • CUDA GPU - ~ 330 Гц

Я затрудняюсь объяснить, почему версия процессора будет быстрее чем GPU. В этом случае код ядра, который выполняется (в случае CL) на CPU и GPU, идентичен. Во время инициализации я выбираю либо процессор, либо 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 в этом ядре плохо, но даже в результате трубопровод стойло не должно вызывать такую ​​крайнюю дельту производительность.

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

+0

У вас есть образец кода, который вы можете поделиться? –

+0

@ EricBainville Конечно - вы хотите C#, ядра CUDA или CL, или что? (Это полу-среднее приложение. Я не хочу вставлять 20k строк кода в SO) –

+10

Я не вижу никаких признаков того, что ядро ​​cuda использует более 1 потока на блок (нет необходимости использовать 'threadIdx.x' или' threadIdx.y'). Кроме того, запуск задает 1 поток на блок. Это означает, что около 97% возможностей графического процессора не используется. Я не знаю много о cudafy, поэтому я не знаю, есть ли у вас контроль над этим, но я вовсе не удивлен, что код cuda не работает впечатляюще быстро. –

ответ

7

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

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

Типичные размеры резьбовых блоков должны составлять не менее 128 потоков на блок, а выше часто бывает лучше, в кратных 32, до предела 512 или 1024 на блок, в зависимости от графического процессора.

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

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

Смежные вопросы