OpenACC красно-черный Gauss-Seidel медленнее, чем CPU

Я добавил директивы OpenACC в свой красно-черный решатель Гаусса-Зейделя для уравнения Лапласа (простая задача с нагретой пластиной), но код с ускорением на GPU не быстрее, чем на CPU, даже для больших задач.

Я также написал версию CUDA, и она намного быстрее обеих (для 512x512 порядка 2 секунд по сравнению с 25 для CPU и OpenACC).

Кто-нибудь может подумать о причине такого несоответствия? Я понимаю, что CUDA предлагает наибольшую потенциальную скорость, но OpenACC должен давать что-то лучшее, чем CPU, для более крупных задач (например, решатель Jacobi для того же типа проблем, продемонстрированный здесь).

Вот соответствующий код (полный рабочий источник находится здесь):

#pragma acc data copyin(aP[0:size], aW[0:size], aE[0:size], aS[0:size], aN[0:size], b[0:size]) copy(temp_red[0:size_temp], temp_black[0:size_temp])
// red-black Gauss-Seidel with SOR iteration loop
for (iter = 1; iter <= it_max; ++iter) {
  Real norm_L2 = 0.0;

  // update red cells
  #pragma omp parallel for shared(aP, aW, aE, aS, aN, temp_black, temp_red) \
      reduction(+:norm_L2)
  #pragma acc kernels present(aP[0:size], aW[0:size], aE[0:size], aS[0:size], aN[0:size], b[0:size], temp_red[0:size_temp], temp_black[0:size_temp])
  #pragma acc loop independent gang vector(4)
  for (int col = 1; col < NUM + 1; ++col) {
    #pragma acc loop independent gang vector(64)
    for (int row = 1; row < (NUM / 2) + 1; ++row) {

      int ind_red = col * ((NUM / 2) + 2) + row;        // local (red) index
      int ind = 2 * row - (col % 2) - 1 + NUM * (col - 1);  // global index

      #pragma acc cache(aP[ind], b[ind], aW[ind], aE[ind], aS[ind], aN[ind])

      Real res = b[ind] + (aW[ind] * temp_black[row + (col - 1) * ((NUM / 2) + 2)]
                         + aE[ind] * temp_black[row + (col + 1) * ((NUM / 2) + 2)]
                         + aS[ind] * temp_black[row - (col % 2) + col * ((NUM / 2) + 2)]
                         + aN[ind] * temp_black[row + ((col + 1) % 2) + col * ((NUM / 2) + 2)]);

      Real temp_old = temp_red[ind_red];
      temp_red[ind_red] = temp_old * (1.0 - omega) + omega * (res / aP[ind]);

      // calculate residual
      res = temp_red[ind_red] - temp_old;
      norm_L2 += (res * res);

    } // end for row
  } // end for col

  // update black cells
  #pragma omp parallel for shared(aP, aW, aE, aS, aN, temp_black, temp_red) \
          reduction(+:norm_L2)
  #pragma acc kernels present(aP[0:size], aW[0:size], aE[0:size], aS[0:size], aN[0:size], b[0:size], temp_red[0:size_temp], temp_black[0:size_temp])
  #pragma acc loop independent gang vector(4)
  for (int col = 1; col < NUM + 1; ++col) {
    #pragma acc loop independent gang vector(64)
    for (int row = 1; row < (NUM / 2) + 1; ++row) {

      int ind_black = col * ((NUM / 2) + 2) + row;      // local (black) index
      int ind = 2 * row - ((col + 1) % 2) - 1 + NUM * (col - 1);    // global index

      #pragma acc cache(aP[ind], b[ind], aW[ind], aE[ind], aS[ind], aN[ind])

      Real res = b[ind] + (aW[ind] * temp_red[row + (col - 1) * ((NUM / 2) + 2)]
                         + aE[ind] * temp_red[row + (col + 1) * ((NUM / 2) + 2)]
                         + aS[ind] * temp_red[row - ((col + 1) % 2) + col * ((NUM / 2) + 2)]
                         + aN[ind] * temp_red[row + (col % 2) + col * ((NUM / 2) + 2)]);

      Real temp_old = temp_black[ind_black];
      temp_black[ind_black] = temp_old * (1.0 - omega) + omega * (res / aP[ind]);

      // calculate residual
      res = temp_black[ind_black] - temp_old;       
      norm_L2 += (res * res);

    } // end for row
  } // end for col

  // calculate residual
  norm_L2 = sqrt(norm_L2 / ((Real)size));

  if(iter % 100 == 0) printf("%5d, %0.6f\n", iter, norm_L2);

  // if tolerance has been reached, end SOR iterations
  if (norm_L2 < tol) {
    break;
  }
}

person Kyle Niemeyer    schedule 19.10.2012    source источник
comment
Хорошо, когда я увеличиваю размер задачи до 1024x1024 (NUM=1024), результаты становятся более благоприятными: OpenMP с 4 потоками ЦП завершает работу примерно за 71 секунду, в то время как OpenACC делает это примерно за 50. Это все еще намного медленнее, чем CUDA (около 22 с), однако.   -  person Kyle Niemeyer    schedule 19.10.2012


Ответы (2)


Хорошо, я нашел полурешение, которое несколько значительно сокращает время для небольших задач.

Если я вставлю строки:

acc_init(acc_device_nvidia);
acc_set_device_num(0, acc_device_nvidia);

прежде чем я запущу свой таймер, чтобы активировать и настроить графический процессор, время для проблемы 512x512 падает до 9,8 секунд и до 42 для 1024x1024. Дальнейшее увеличение размера задачи показывает, насколько быстро даже OpenACC можно сравнить с работой на четырех ядрах ЦП.

С этим изменением код OpenACC работает примерно в 2 раза медленнее, чем код CUDA, при этом разрыв становится чуть медленнее (~ 1,2) по мере того, как размер задачи становится все больше и больше.

person Kyle Niemeyer    schedule 19.10.2012
comment
acc_init занимает 1~2 секунды. Сколько нужно для запуска задач 512x512 и 1024x1024 на процессоре? - person lashgar; 20.10.2012
comment
Как я уже упоминал, для проблемы с 512x512 без acc_init перед запуском таймера код OpenACC выполнялся примерно за 25 секунд. С ним после запуска таймера он упал примерно до 9,8 секунды. Для 1024x1024 оно увеличилось с 50 до 42 секунд. Я читал в другом месте, что acc_init должен занимать всего несколько секунд, но влияние на мой код (для более мелких проблем), похоже, больше, чем это. - person Kyle Niemeyer; 21.10.2012

Я загружаю ваш полный код, скомпилирую и запускаю его! Не прекращал бежать и за инструкцией

if(iter % 100 == 0) printf(%5d, %0.6f\n, iter, norm_L2);

результат был:

100, нан

200, нан

....

Я изменил все переменные с типом Real на тип float, и результат был таким:

100, 0.000654

200, 0.000370

..., ....

..., ....

8800, 0.000002

8900, 0.000002

9000, 0.000001

9100, 0.000001

9200, 0.000001

9300, 0.000001

9400, 0.000001

9500, 0.000001

9600, 0.000001

9700, 0.000001

ЦПУ

Итераций: 9796

Общее время: 5,594017 с

При NUM = 1024 результат был таким:

Итераций: 27271

Общее время: 25,949905 с

person pg1927    schedule 25.11.2012