Я оцениваю накладные расходы (в настенные часы) некоторых функций в программах Fortran. И я столкнулся со следующим поведением с GNU fortran, которого я не ожидал: наличие подпрограммы в том же файле, что и у основной программы (в области содержимого или в модуле), против наличия подпрограммы в отдельном модуле (в отдельном файле). ) имеет большое влияние.
Простой код, который воспроизводит поведение: у меня есть подпрограмма, которая выполняет умножение матрицы на вектор 250000 раз. В первом тесте у меня есть подпрограмма в области содержимого основной программы. Во втором тесте та же подпрограмма находится в отдельном модуле. Разница в производительности между ними велика.
Подпрограмма в области содержимого основной программы, 10 прогонов приводит к
min: 1.249
avg: 1.266
1.275 - 1.249 - 1.264 - 1.279 - 1.266 - 1.253 - 1.271 - 1.251 - 1.269 - 1.284
Подпрограмма в отдельном модуле, 10 прогонов приводит к
min: 1.848
avg: 1.861
1.848 - 1.862 - 1.853 - 1.871 - 1.854 - 1.883 - 1.810 - 1.860 - 1.886 - 1.884
Примерно на 50% медленнее, этот коэффициент кажется совместимым как с размером матрицы, так и с числом итераций. эти тесты сделаны с gfortran 4.8.5. С gfortran 8.3.0 программа работает немного быстрее, но время удваивается от подпрограммы в разделе Содержать основной программы к подпрограмме в отдельном модуле.
В группе портландов такой проблемы нет моя тестовая программа, и она работает даже быстрее, чем лучший случай gfortran.
Если я читаю размер матрицы из входного файла (или аргумента командной строки времени выполнения) и выполняю динамическое распределение c, тогда разница во времени настенных часов исчезает, и оба случая работают медленнее (настенные часы) время подпрограммы в отдельном модуле, отдельный файл). Я подозреваю, что gfortran способен оптимизировать основную программу лучше, если размер матрицы известен во время компиляции в основной программе.
Что я делаю неправильно, что не нравится компиляторам GNU или что такое GNU компилятор делает плохо? Существуют ли флаги компиляции, чтобы помочь gfortran в таких случаях?
Все скомпилировано с оптимизацией -O3
Код (test_simple.f90)
!< @file test_simple.f90
!! simple test
!>
!
program test_simple
!
use iso_fortran_env
use test_mod
!
implicit none
!
integer, parameter :: N = 100
integer, parameter :: N_TEST = 250000
logical, parameter :: GENERATE=.false.
!
real(real64), parameter :: dx = 10.0_real64
real(real64), parameter :: lx = 40.0_real64
!
real(real64), dimension(N,N) :: A
real(real64), dimension(N) :: x, y
real(real64) :: start_time, end_time
real(real64) :: duration
!
integer :: k, loop_idx
!
call make_matrix(A,dx,lx)
x = A(N/2,:)
!
y = 0
call cpu_time( start_time )
call axpy_loop (A, x, y, N_TEST)
!call axpy_loop_in (A, x, y, N_TEST)
!
call cpu_time( end_time )
!
duration = end_time-start_time
!
if( duration < 0.01 )then
write( *, "('Total time:',f10.6)" ) duration
else
write( *, "('Total time:',f10.3)" ) duration
end if
!
write(*,"('Sum = ',ES14.5E3)") sum(y)
!
contains
!
!< @brief compute y = y + A^nx
!! @param[in] A matrix to use
!! @param[in] x vector to used
!! @param[in, out] y output
!! @param[in] nloop number of iterations, power to apply to A
!!
!>
subroutine axpy_loop_in (A, x, y, nloop)
real(real64), dimension(:,:), intent(in) :: A
real(real64), dimension(:), intent(in) :: x
real(real64), dimension(:), intent(inout) :: y
integer, intent(in) :: nloop
!
real(real64), dimension(size(x)) :: z
integer :: k, iter
!
y = x
do iter = 1, nloop
z = y
y = 0
do k = 1, size(A,2)
y = y + A(:,k)*z(k)
end do
end do
!
end subroutine axpy_loop_in
!
!> @brief Computes the square exponential correlation kernel matrix for
!! a 1D uniform grid, using coordinate vector and scalar parameters
!! @param [in, out] C square matrix of correlation (kernel)
!! @param [in] dx grid spacing
!! @param [in] lx decorrelation length
!!
!! The correlation betwen the grid points i and j is given by
!! \f$ C(i,j) = \exp(\frac{-(xi-xj)^2}{2l_xi l_xj}) \f$
!! where xi and xj are respectively the coordinates of point i and j
!>
subroutine make_matrix(C, dx, lx)
! some definitions of the square correlation
! uses 2l^2 while other use l^2
! l^2 is used here by setting this factor to 1.
real(real64), parameter :: factor = 1.0
!
real(real64), dimension(:,:), intent(in out) :: C
real(real64), intent(in) :: dx
real(real64) lx
! Local variables
real(real64), dimension(size(x)) :: nfacts
real :: dist, denom
integer :: ii, jj
!
do jj=1, size(C,2)
do ii=1, size(C,1)
dist = (ii-jj)*dx
denom = factor*lx*lx
C(ii, jj) = exp( -dist*dist/denom )
end do
! compute normalization factors
nfacts(jj) = sqrt( sum( C(:, jj) ) )
end do
!
! normalize to prevent arbitrary growth in those tests
! where we apply the exponential of the matrix
do jj=1, size(C,2)
do ii=1, size(C,1)
C(ii, jj) = C(ii, jj)/( nfacts(ii)*nfacts(jj) )
end do
end do
! remove the very small
where( C<epsilon(1.) ) C=0.
!
end subroutine make_matrix
!
end program test_simple
!
Код (test_mod. f90)
!> @file test_mod.f90
!! simple operations
!<
!< @brief module for simple operations
!!
!>
module test_mod
use iso_fortran_env
implicit none
contains
!
!< @brief compute y = y + A^nx
!! @param[in] A matrix to use
!! @param[in] x vector to used
!! @param[in, out] y output
!! @param[in] nloop number of iterations, power to apply to A
!!
!>
subroutine axpy_loop( A, x, y, nloop )
real(real64), dimension(:,:), intent(in) :: A
real(real64), dimension(:), intent(in) :: x
real(real64), dimension(:), intent(inout) :: y
integer, intent(in) :: nloop
!
real(real64), dimension(size(x)) :: z
integer :: k, iter
!
y = x
do iter = 1, nloop
z = y
y = 0
do k = 1, size(A,2)
y = y + A(:,k)*z(k)
end do
end do
!
end subroutine axpy_loop
!
end module test_mod
скомпилировать как
gfortran -O3 -o simple test_mod.f90 test_simple.f90
запустить как
./simple