2016-03-19 2 views
0

В основной части моего Fortran кода у меня есть эти строкиСделать подпрограмму быстрее

Gmat=0 
do i=1,indCompMax 
do j=(i-1)*useSymFlag+1,nsit-(i-1)*useSymFlag 
l=1 
do while (G0poles(l,2)/=0) 
Gmat(i,j)=Gmat(i,j)+real(G0int(i,j,l))/(omega(k)-G0poles(l,1))**G0poles(l,3) 
l=l+1 
enddo 
enddo 
enddo 
call ExtendBySymmetry(Gmat) 

Эта часть повторяются несколько раз в коде, так что я определил эту подпрограмму

!============================================================================= 
SUBROUTINE EvaluateFunc(matrixPol,matrixInt,z,matrix) 
     use NAGmodule 
     integer i,j,k 
     REAL*8, DIMENSION(Npoles,3) :: matrixPol 
     COMPLEX*16, DIMENSION(nsit,nsit,Npoles) :: matrixInt 
     COMPLEX*16, DIMENSION(nsit,nsit) :: matrix 
     COMPLEX*16 :: z 

    do i=1,indCompMax 
    do j=(i-1)*useSymFlag+1,nsit-(i-1)*useSymFlag 
     k=1 
     do while (matrixPol(k,2)/=0) 
     matrix(i,j)=matrix(i,j)+real(matrixInt(i,j,k))/(z-matrixPol(k,1))**matrixPol(k,3) 
     k=k+1 
     enddo 
    enddo 
    enddo 
    call ExtendBySymmetry(matrix) 

end 

Проблема заключается в том что, если я использую эту подпрограмму, оценка выходной матрицы займет гораздо больше времени (примерно в 5 раз медленнее), чем та же оценка, но непосредственно делается в основной части кода. Как я могу оптимизировать код и сделать оценку с помощью подпрограммы быстрее?

ОБНОВЛЕНИЕ: Спасибо за ответ. Во-первых, операция **matrixPol(k,3) присутствует также в основном коде, я забыл написать ее в сообщении.

Для сравнения (matrixPol(k,2)/=0) нет проблем, поскольку на самом деле, начиная с определенного положения вектора, все элементы равны нулю.

Вычисление префактора вне цикла i,j помогло ускорить подпрограмму. И переключение двух индексов i и j практически не влияет. Вот бегущие времена подпрограммы

Все в основной части 1.688s

моей старая подпрограмма 19.063s

с factor вне цикла I, J 5.193s

Switching индексы i и j 5.281s

с dot_product 4.958s

Но подпрограмма все еще более чем на 2,5 раза медленнее.

Вот минимальный пример:

module NAGmodule 
    implicit none 
    real*8,  allocatable :: hmat(:,:),eval(:),eigmat(:,:) 
    real*8,  allocatable :: G0poles(:,:) 
    complex*16, allocatable :: G0int(:,:,:) 
    complex*16, allocatable :: Gmat(:,:) 
    real*8,  allocatable :: omega(:) 
    integer     :: numpoles, halffillingflag, iter, indCompMax 
    complex*16    :: omegaComplex 
    real*8, parameter  :: pi=3.141592653589793 
    integer, parameter  :: out_unit=10 
    integer, parameter  :: timeFag=1 
    integer     :: counti, countf, count_rate 
    real     :: dt 
    integer, parameter :: Npoles=1000 
    real*8, parameter :: U=4 
    real*8, parameter :: omegamin=-20 
    real*8, parameter :: omegamax=20 
    integer, parameter :: Nomega=1500000 
    integer, parameter :: nsit = 4 
    integer, parameter :: nup = 1 
    integer, parameter :: ndw = 1 
    integer, parameter :: PBCflag=1 
    integer, parameter :: useSymFlag=1 
    end module NAGmodule 

    use nagmodule 
    integer     :: i,j,k,l,m,n,p,q 
    REAL*8 t1,t2 

    allocate(hmat(nsit,nsit),eigmat(nsit,nsit),eval(nsit)) 
    allocate(G0poles(Npoles,3),G0int(nsit,nsit,Npoles)) 
    allocate(omega(Nomega)) 
    allocate(Gmat(nsit,nsit)) 

    indCompMax=1 

    hmat=0. 
    do i=1,(nsit-1) 
     hmat(i,i+1)=-1 
     hmat(i+1,i)=-1 
    enddo 
    if (PBCflag==1) then 
     hmat(1,nsit)=-1 
     hmat(nsit,1)=-1 
    end if 

    call NAGdiag(nsit) 
    eigmat=hmat 

    do k=1,Nomega 
     omega(k)=(omegamax-omegamin)/(Nomega-1)*(k-1)+omegamin 
    enddo 

    do k=1,nup 
     G0poles(k,1)=eval(k) 
     G0poles(k,2)=-1 
     G0poles(k,3)=1 
    enddo 
    do k=(nup+1),nsit 
     G0poles(k,1)=eval(k) 
     G0poles(k,2)=1 
     G0poles(k,3)=1 
    enddo 


     do k=1,nsit 
     G0int(k,k,k)=1 
     if ((k==nup).AND.(abs(eval(k)-eval(k+1))<1e-15)) then 
      G0int(k,k,k)=0.5 
      G0int(k+1,k+1,k)=0.5 
     else if ((k==nup+1).AND.(abs(eval(k)-eval(k-1))<1e-15)) then 
      G0int(k,k,k)=0.5 
      G0int(k-1,k-1,k)=0.5 
     end if 
     enddo 

    do k=1,nsit 
    G0int(:,:,k)=matmul(eigmat,matmul(G0int(:,:,k),transpose(eigmat))) 
    enddo 


    t1=0 
    t2=0 


    do k=1,Nomega 
    omegaComplex=CMPLX(omega(k),0) 
    call system_clock(counti,count_rate) 
    Gmat=0 
    call EvaluateFunc3(G0poles,G0int,omegaComplex,Gmat) 
    call system_clock(countf) 
    dt=REAL(countf-counti)/REAL(count_rate) 
    t1=t1+dt 

    call system_clock(counti,count_rate) 
     Gmat=0 
     do i=1,indCompMax 
     do j=(i-1)*useSymFlag+1,nsit-(i-1)*useSymFlag 
      l=1 
      do while (G0poles(l,2)/=0) 
      Gmat(i,j)=Gmat(i,j)+real(G0int(i,j,l))/(omega(k)-G0poles(l,1)) 
      l=l+1 
      enddo 
     enddo 
     enddo 
     call ExtendBySymmetry(Gmat) 
    call system_clock(countf) 
    dt=REAL(countf-counti)/REAL(count_rate) 
    t2=t2+dt 
    enddo 

    write(*,*)'time with subroutine',t1 
    write(*,*)'time main',t2 


    stop 
    end 

    !================================================================================= 
    SUBROUTINE EvaluateFunc3(matrixPol,matrixInt,z,matrix) 
      use NAGmodule 
      integer i,j,k 
      REAL*8, DIMENSION(Npoles,3) :: matrixPol 
      COMPLEX*16, DIMENSION(nsit,nsit,Npoles) :: matrixInt 
      COMPLEX*16, DIMENSION(nsit,nsit) :: matrix 
      COMPLEX*16 :: z 
      integer :: maxPoles 
      COMPLEX*16, DIMENSION(Npoles) :: factor 


    maxPoles=0 
    do while (matrixPol(maxPoles+1,2)/=0) 
    maxPoles=maxPoles+1 
    enddo 

     factor(:maxPoles) = (1.,0.)/(z-matrixPol(:maxPoles,1))**matrixPol(:maxPoles,3) 

     do j=1,indCompMax 
     do i=(j-1)*useSymFlag+1,nsit-(j-1)*useSymFlag 
      matrix(i,j)=matrix(i,j)+dot_product(matrixInt(i,j,1:maxPoles),factor(1:maxPoles)) 
     enddo 
     enddo 
     call ExtendBySymmetry2(matrix) 

    end 

    !================================================================================= 
    SUBROUTINE ExtendBySymmetry2(matrix) 
      use NAGmodule 
      COMPLEX*16, DIMENSION(nsit,nsit) :: matrix 
      integer k,i,j,l,m 


    if ((PBCflag==1).AND.(useSymFlag==1)) then 
      do i=2,nsit 
      matrix(2:nsit,i)=matrix(1:nsit-1,i-1) 
      matrix(1,i)=matrix(nsit,i-1) 
      enddo 
    else if ((PBCflag==0).AND.(useSymFlag==1)) then 
      do j=1,nsit/2 
      do i=j,nsit-j+1 
       matrix(j,i)=matrix(i,j) 
       matrix(nsit-i+1,nsit-j+1)=matrix(i,j) 
      matrix(nsit-j+1,nsit-i+1)=matrix(i,j) 
      enddo 
      enddo 
    end if 

    end 

    !================================================================================= 
    SUBROUTINE ExtendBySymmetry(matrix) 
      use NAGmodule 
      COMPLEX*16, DIMENSION(nsit,nsit) :: matrix 
      integer k,i,j,l,m 


    if ((PBCflag==1).AND.(useSymFlag==1)) then 
      do i=2,nsit 
      matrix(i,2:nsit)=matrix(i-1,1:nsit-1) 
      matrix(i,1)=matrix(i-1,nsit) 
      enddo 
    else if ((PBCflag==0).AND.(useSymFlag==1)) then 
      do i=1,nsit/2 
      do j=i,nsit-i+1 
       matrix(j,i)=matrix(i,j) 
       matrix(nsit-i+1,nsit-j+1)=matrix(i,j) 
       matrix(nsit-j+1,nsit-i+1)=matrix(i,j) 
      enddo 
      enddo 
    end if 

    end 


    !================================================================================= 

      SUBROUTINE NAGdiag(nsit1) 
      use NAGmodule 

      real*8, allocatable :: WORK(:) 
      integer, allocatable :: IWORK(:) 

      CHARACTER JOB, UPLO 
      EXTERNAL dsyevd 
      NMAX=nsit1 
      LDA=NMAX 
      LWORK=4*NMAX*NMAX+100 
      LIWORK=5*NMAX 
      LIWORK=10*NMAX  
      ALLOCATE(WORK(LWORK),IWORK(LIWORK)) 

      JOB='V'  
      UPLO='L' 

      CALL dsyevd(JOB,UPLO,nsit1,hmat,LDA,eval,WORK,LWORK,IWORK,LIWORK,INFO) 

      IF (INFO.GT.0) THEN 
      WRITE (*,*) 'Failure to converge.' 
      stop 
     endif 

      deALLOCATE(WORK,IWORK) 

      return 
      end` 
+1

Вычисляющий код в первом фрагменте и в подпрограмме, похоже, отличается. –

+0

Не следует ли «вызывать ExtendBySymmetry (matrix)' в подпрограмме? –

+0

да, извините, другая опечатка – user3368447

ответ

3

Из-за нескольких изменений исходного вопроса ответ частично лишний. Тем не менее, часть оптимизации остается в силе.

Настоящая проблема заключается в том, что вы передаете z в качестве комплексного номера в подпрограмму (omegaComplex), в то время как omega(k) является реальной. Это приводит к тому, что возведение в степень и деление выполняются как сложные операции, а не реальные.

Фиксация z будет реальной (и factor в оптимизации также) приводит к ожидаемым результатам.С оптимизацией я получаю:

time with subroutine 0.24000001139938831  
time main 0.35700001695659012 

Оригинальный ответ:

Прежде всего, подпрограмма не делает те же операции, что ваш внутренний код делает. Операция **matrixPol(k,3) является мощью комплексного числа, которое связано с тяжелым вычислительным усилием. Неудивительно, что подпрограмма намного медленнее.

Я вижу несколько возможностей, чтобы ускорить ваш код:

  • Делитель (z-matrixPol(k,1))**matrixPol(k,3) не зависит от i и j и может быть вынут из петли.
  • Подразделения стоят дороже, чем умножения. Поэтому вы должны предварительно вычислить factor = 1/div вне цикла и умножить на factor в цикле.
  • Сравнение (matrixPol(k,2)/=0) почти никогда не будет истинным, если вы не установите соответствующие значения точно равными нулю. Я предполагаю, что вы знаете порядок своих полюсов, прежде чем вы вызовете подпрограмму, так почему бы не передать ее и не сохранить это сравнение? Если это невозможно, определите количество полюсов внутри подпрограммы перед основным циклом. Тогда внутренняя петля над k намного проще.
  • Внутри цикла вы конвертируете входную матрицу в real снова и снова. Это можно сделать за пределами цикла за один раз. Или, еще лучше, просто передайте только действительную часть функции!

На данный момент ваш код выглядит примерно так:

!============================================================================= 
SUBROUTINE EvaluateFunc(matrixPol,matrixInt,z,matrix) 
     use NAGmodule 
     integer i,j,k 
     REAL*8, DIMENSION(Npoles,3) :: matrixPol 
     COMPLEX*16, DIMENSION(nsit,nsit,Npoles) :: matrixInt 
     COMPLEX*16, DIMENSION(nsit,nsit) :: matrix 
     COMPLEX*16 :: z, factor(Npoles) 
     REAL*8, DIMENSION(nsit,nsit,Npoles) :: matrixInt_re 
     integer :: maxPoles 

    ! Determine maximum number of poles 
    do k=1,Npoles 
    ! Only valid if the poles are set to exactly zero outside. If not, 
    ! use (abs(matrixPol(k,2)) <= someEpsilon) 
    if (matrixPol(k,2) == 0) then 
     maxPoles = k-1 
     exit 
    endif 
    enddo 

    ! Pre-compute factors 
    factor(:maxPoles) = (1.,0.)/(z-matrixPol(:maxPoles,1))**matrixPol(:maxPoles,3) 
    ! Convert input to real 
    matrixInt_re = real(matrixInt) 

    do i=1,indCompMax 
    do j=i,nsit-i+1 
     do k=1,maxPoles 
     matrix(i,j)=matrix(i,j)+matrixInt_re(i,j,k)*factor(k) 
     enddo 
    enddo 
    enddo 
    call ExtendBySymmetry(Gmat)  
end 

Дальнейшая оптимизация:

  • переписывания кода, как это становится очевидным, что внутренний цикл по k не что иное, точечный продукт. Это может потенциально ускорить компилятор. Основной цикл будет выглядеть так:
do i=1,indCompMax 
    do j=i,nsit-i+1 
     matrix(i,j)=matrix(i,j) + & 
     dot_product(matrixInt_re(i,j,:maxPoles), factor(:maxPoles)) 
    enddo 
    enddo 
  • Как отмечалось chw21, Fortran использует компоновку column major памяти, и вы к нему доступ в ряд крупных моды. Это потенциально теряет много памяти. Вы должны рассмотреть возможность переноса ваших массивов в основную программу или, по крайней мере, переключить петли на i и j. Я бы предпочел первый вариант, так как продукт внутренней точки затем будет выполняться на смежных кусках памяти.
+0

Спасибо за ответ. Во-первых, операция '** matrixPol (k, 3)' присутствует также в основном коде, я забыл написать ее в сообщении. – user3368447

+0

@ user3368447 Помимо добавления экспоненциальности в основной код, вы также изменили число итераций во внутренней функции подпрограммы. Я подозреваю, что это также изменяет тайминги. Не могли бы вы повторить сравнение? –

+0

Да, извините, а другая итерация была опечаткой в ​​сообщении. Тайминги получены с теми же итерациями по i и j – user3368447

2

Try, чтобы увидеть, можно ли поменять местами петли вокруг. Так как Fortran хранит массивы в заказе

(1, 1), (2, 1), (3, 1), ..., (n, 1), (1, 2), (2, 2), ... 

Доступ к памяти намного быстрее, если вы пройдете по этому измерению.

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