Почему внутренняя функция Фортрана «распространяется» часто медленнее, чем явная итерация - PullRequest
2 голосов
/ 17 апреля 2019

Я работаю с геофизическими моделями, и в обычной ситуации необходимо умножать, добавлять и т. Д. 2D-данные с 3D-данными.Ниже приведен пример.

module benchmarks
  implicit none
  integer, parameter :: n=500
  integer :: k
  real :: d2(n,n)
  real :: d3(n,n,n)
  contains
  ! Iteration
  subroutine benchmark_a(res)
    real, intent(out) :: res(n,n,n)
    do k = 1, size(d3,3)
      res(:,:,k) = d2*d3(:,:,k)
    end do
  end subroutine
  ! Spread
  subroutine benchmark_b(res)
    real, intent(out) :: res(n,n,n)
    res = d3*spread(d2, 3, size(d3,3))
  end subroutine
end module

program main
  use benchmarks
  real :: t, tarray(2)
  real :: res(n,n,n)
  call random_number(d2)
  call random_number(d3)
  ! Iteration
  call dtime(tarray, t)
  call benchmark_a(res)
  call dtime(tarray, t)
  write(*,*) 'Iteration', t
  ! Spread
  call dtime(tarray, t)
  call benchmark_b(res)
  call dtime(tarray, t)
  write(*,*) 'Spread', t
end program

Когда я запускаю это с переменным размером измерения n, я обычно нахожу, что spread намного медленнее;например:

Spread   2.09942889
Iteration  0.458283991

Кто-нибудь знает, почему подход spread, а не явный цикл for (который я думал, как правило, следует избегать любой ценой), намного медленнее?

1 Ответ

4 голосов
/ 17 апреля 2019

Основной ответ здесь "это не так". Может быть, с конкретным компилятором и конкретными обстоятельствами, встроенная функция не так хорошо оптимизирована, как явный цикл DO, но так быть не должно. Я тестировал с помощью ifort 19, и даже на уровнях оптимизации по умолчанию внутренняя функция SPREAD и явный цикл генерировали похожий код, причем внутренняя функция была быстрее, когда я исправляю программу для использования результата.

Iteration 0.2187500 0.1376885 Spread 9.3750000E-02 0.1376885

Я бы также предупредил (как я это сделал в комментариях к вашему вопросу), что упрощенные эталонные программы часто не измеряют то, что, по мнению автора, они делают. Самая распространенная ошибка, которую демонстрируют оба ваших исходных и пересмотренных примера, заключается в том, что результат тестируемой работы никогда не используется, поэтому достаточно умный компилятор может просто испарить всю операцию. Действительно, когда я создаю оба ваших тестовых примера с помощью ifort 19, компилятор полностью удаляет всю работу, оставляя только временный код. Само собой разумеется, это бежит довольно быстро.

  implicit none
  integer, parameter :: n=500
  integer :: k
  real :: d2(n,n)
  real :: d3(n,n,n)
  contains
  ! Iteration
  subroutine benchmark_a(res)
    real, intent(out) :: res(n,n,n)
    do k = 1, size(d3,3)
      res(:,:,k) = d2*d3(:,:,k)
    end do
  end subroutine
  ! Spread
  subroutine benchmark_b(res)
    real, intent(out) :: res(n,n,n)
    res = d3*spread(d2, 3, size(d3,3))
  end subroutine
end module

program main
  use benchmarks
  real :: tstart,tend
  real :: res(n,n,n)
  call random_number(d2)
  call random_number(d3)
  ! Iteration
  call cpu_time(tstart)
  call benchmark_a(res)
  call cpu_time(tend)
  write(*,*) 'Iteration', tend-tstart, res(10,10,10)
  ! Spread
  call cpu_time(tstart)
  call benchmark_b(res)
  call cpu_time(tend)
  write(*,*) 'Spread', tend-tstart, res(10,10,10)
end program```
...