Большие издержки, когда подпрограмма находится в отдельном модуле по сравнению с тем же файлом, что и основная программа - PullRequest
2 голосов
/ 13 февраля 2020

Я оцениваю накладные расходы (в настенные часы) некоторых функций в программах 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

1 Ответ

0 голосов
/ 24 февраля 2020

Комбинация флагов -march=native и -flto является решением проблемы, по крайней мере, на моих тестовых компьютерах. С этими опциями программа полностью оптимизируется, и нет разницы между наличием подпрограммы в том же файле, что и основная программа, или в отдельном файле (отдельный модуль). Кроме того, время выполнения сравнимо с временем выполнения с компилятором Portland Group. Ни один из этих вариантов сам по себе не решил проблему. -march=native сам по себе ускоряет встроенную версию, но ухудшает версию модуля.

Мое предубеждение заключается в том, что опция -march=native должна быть по умолчанию; пользователи, занимающиеся чем-то другим, имеют опыт и знают, что они делают, поэтому они могут добавить соответствующую опцию или отключить настройку по умолчанию, в то время как обычный пользователь не будет легко думать об этом.

Спасибо за все комментарии.

...