Скорость разыменования свойств класса в фортране

Я ожидаю ответа, как не волнуйтесь, об этом позаботится компилятор, но я не уверен.

Когда я создаю какой-то метод в каком-то пользовательском типе / классе в Фортране, происходит ли какое-либо снижение производительности из-за ссылок на поля / разыменования объекта, например this%a(i) = this%b(i) + this%c(i) по сравнению с просто работать с массивами, как a(i) = b(i) + c(i)

более сложный пример:

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

type grid3D                                         ! 3D grid maps of observables
  real,    dimension (3) :: Rmin, Rmax, Rspan, step ! grid size and spacing (x,y,z)
  integer, dimension (3) :: N                       ! dimension in x,y,z
  real, dimension (3,:, :, :), allocatable :: f     ! array storing values of othe observable
  contains
    procedure :: interpolate => grid3D_interpolate
end type grid3D

function grid3D_interpolate(this, R ) result(ff)
 implicit none
  ! variables
  class (grid3D) :: this
  real, dimension (3), intent (in)   :: R
  real :: ff
  integer ix0,iy0,iz0
  integer ix1,iy1,iz1
  real dx,dy,dz
  real mx,my,mz
  ! function body
  ix0 = int( (R(1)/this%step(1)) + fastFloorOffset ) - fastFloorOffset
  iy0 = int( (R(2)/this%step(2)) + fastFloorOffset ) - fastFloorOffset
  iz0 = int( (R(3)/this%step(3)) + fastFloorOffset ) - fastFloorOffset
  dx = R(1) - x0*this%step(1)
  dy = R(2) - y0*this%step(2)
  dz = R(3) - z0*this%step(3)
  ix0 = modulo( x0   , this%N(1) )+1
  iy0 = modulo( y0   , this%N(2) )+1
  iz0 = modulo( z0   , this%N(3) )+1
  ix1 = modulo( x0+1 , this%N(1) )+1
  iy1 = modulo( y0+1 , this%N(2) )+1
  iz1 = modulo( z0+1 , this%N(3) )+1
  mx=1.0-dx
  my=1.0-dy
  mz=1.0-dz
  ff =    mz*(my*(mx*this%f(ix0,iy0,iz0)     &
                 +dx*this%f(ix1,iy0,iz0))    &
             +dy*(mx*this%f(ix0,iy1,iz0)     &
                 +dx*this%f(ix1,iy1,iz0)))   &
         +dz*(my*(mx*this%f(ix0,iy0,iz1)     &
                 +dx*this%f(ix1,iy0,iz1))    &
             +dy*(mx*this%f(ix0,iy1,iz1)     &
                 +dx*this%f(ix1,iy1,iz1)))
  end if
end function grid3D_interpolate

end module T_grid3Dvec

1 ответ

Решение

На самом деле, нет.

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

Вот несколько примеров:

module vec_mod
  implicit none

  type t_vector
    real :: x = 0.
    real :: y = 0.
    real :: z = 0.
  end type

  type t_group
    type(t_vector),allocatable :: vecs(:)
  end type

contains

  subroutine sum_vec( vecs, res )
    implicit none
    type(t_vector),intent(in)   :: vecs(:)
    type(t_vector),intent(out)  :: res
    integer                     :: i

    res%x = 0. ; res%y = 0. ; res%z = 0.

    do i=1,size(vecs)
      res%x = res%x + vecs(i)%x
      res%y = res%y + vecs(i)%y
      res%z = res%z + vecs(i)%z
    enddo
  end subroutine

  subroutine sum_vec_ptr( vecs, res )
    implicit none
    type(t_vector),intent(in),target   :: vecs(:)
    type(t_vector),intent(out)         :: res
    integer                            :: i
    type(t_vector),pointer             :: curVec

    res%x = 0. ; res%y = 0. ; res%z = 0.

    do i=1,size(vecs)
      curVec => vecs(i)
      res%x = res%x + curVec%x
      res%y = res%y + curVec%y
      res%z = res%z + curVec%z
    enddo
  end subroutine

  subroutine sum_vecGrp( vecGrp, res )
    implicit none
    type(t_group),intent(in)    :: vecGrp
    type(t_vector),intent(out)  :: res
    integer                     :: i

    res%x = 0. ; res%y = 0. ; res%z = 0.

    do i=1,size(vecGrp%vecs)
      res%x = res%x + vecGrp%vecs(i)%x
      res%y = res%y + vecGrp%vecs(i)%y
      res%z = res%z + vecGrp%vecs(i)%z
    enddo
  end subroutine

  subroutine sum_vecGrp_ptr( vecGrp, res )
    implicit none
    type(t_group),intent(in),target    :: vecGrp
    type(t_vector),intent(out)         :: res
    integer                            :: i
    type(t_vector),pointer             :: curVec, vecs(:)

    res%x = 0. ; res%y = 0. ; res%z = 0.

    vecs => vecGrp%vecs
    do i=1,size(vecs)
      curVec => vecs(i)
      res%x = res%x + curVec%x
      res%y = res%y + curVec%y
      res%z = res%z + curVec%z
    enddo
  end subroutine
end module

program test
  use omp_lib
  use vec_mod
  use,intrinsic :: ISO_Fortran_env
  implicit none
  type(t_vector),allocatable :: vecs(:)
  type(t_vector)             :: res
  type(t_group)              :: vecGrp
  integer,parameter          :: N=100000000
  integer                    :: i, stat
  real(REAL64)               :: t1, t2

  allocate( vecs(N), vecGrp%vecs(N), stat=stat )
  if (stat /= 0) stop 'Cannot allocate memory'

  do i=1,N
    call random_number(vecs(i)%x)
    call random_number(vecs(i)%y)
    call random_number(vecs(i)%z)
  enddo

  print *,''
  print *,'1 Level'
  t1 = omp_get_wtime()
  call sum_vec( vecs, res )
  print *,res
  t2 = omp_get_wtime()
  print *,'Normal  [s]:', t2-t1

  t1 = omp_get_wtime()
  call sum_vec_ptr( vecs, res )
  print *,res
  t2 = omp_get_wtime()
  print *,'Pointer [s]:', t2-t1

  print *,''
  print *,'2 Levels'
  vecGrp%vecs = vecs

  t1 = omp_get_wtime()
  call sum_vecGrp( vecGrp, res )
  print *,res
  t2 = omp_get_wtime()
  print *,'Normal  [s]:', t2-t1

  t1 = omp_get_wtime()
  call sum_vecGrp_ptr( vecGrp, res )
  print *,res
  t2 = omp_get_wtime()
  print *,'Pointer [s]:', t2-t1

end program

Скомпилировано с параметрами по умолчанию (gfortran test.F90 -fopenmp) три - небольшое преимущество от разыменования вручную, особенно для двух уровней разыменования:

OMP_NUM_THREADS=1 ./a.out 

 1 Level
   16777216.0       16777216.0       16777216.0    
 Normal  [s]:  0.69216769299237058     
   16777216.0       16777216.0       16777216.0    
 Pointer [s]:  0.67321390099823475     

 2 Levels
   16777216.0       16777216.0       16777216.0    
 Normal  [s]:  0.84902219301147852     
   16777216.0       16777216.0       16777216.0    
 Pointer [s]:  0.71247501399193425   

После включения оптимизации (gfortran test.F90 -fopenmp -O3), вы можете видеть, что компилятор фактически лучше выполняет свою работу автоматически:

OMP_NUM_THREADS=1 ./a.out 

 1 Level
   16777216.0       16777216.0       16777216.0    
 Normal  [s]:  0.13888958499592263     
   16777216.0       16777216.0       16777216.0    
 Pointer [s]:  0.19099253200693056     

 2 Levels
   16777216.0       16777216.0       16777216.0    
 Normal  [s]:  0.13436777899914887     
   16777216.0       16777216.0       16777216.0    
 Pointer [s]:  0.21104205500159878   
Другие вопросы по тегам