问题描述
与此question相关,但我相信此示例可以更清楚地识别该问题。
subroutine ID_OG(N,DETERM)
use variables,only: ID
implicit real (A-H,O-Z)
implicit integer(I-N)
DETERM = 1.0
DO 1 I=1,N
1 ID(I)=0
DETERM = sum(ID)
end subroutine ID_OG
用use variables,only: ID
或real,dimension(N) :: ID
替换real,dimension(:),allocatable :: ID
会导致明显的性能损失。为什么是这样?这是预期的行为吗?我想知道它是否与需要为本地数组ID
重复分配内存的程序有关,而use
语句允许程序跳过内存分配步骤。
在旧式代码ID
中位于module variables
中,但仅在子例程ID_OG
中使用。它在代码的其他任何地方都没有使用-它不是输入或输出。对我来说,将ID
从module variables
中删除并在子例程中本地定义似乎是一种良好的编程习惯。但也许并非如此。
最小工作示例(MWE):
使用gfortran 8.2.0编译为gfortran -O3 test.f95
MODULE variables
implicit none
real,allocatable :: ID
END MODULE variables
program test
use variables
implicit none
integer :: N
integer :: loop_max = 1e6
integer :: ii ! loop index
real :: DETERM
real :: t1,t2
real :: t_ID_OG,t_ID_header,t_ID_no_ID,t_OG_no_ID,t_allocate
character(*),parameter :: format_header = '((A5,1X),20(A12,1X))'
character(*),parameter :: format_data = '((I5,20(ES12.5,1X))'
open(1,file = 'TimingSubroutines_ID.txt',status = 'unkNown')
write(1,format_header) 'N','t_Legacy','t_header','t_head_No_ID','t_Leg_no_ID',&
& 't_allocate'
do N = 1,100
allocate(ID(N))
call cpu_time(t1)
do ii = 1,loop_max
CALL ID_OG(N,DETERM)
end do
call cpu_time(t2)
t_ID_OG = t2 - t1
print*,N,DETERM
call cpu_time(t1)
do ii = 1,loop_max
CALL ID_header(N,DETERM)
end do
call cpu_time(t2)
t_ID_header = t2 - t1
print*,loop_max
CALL ID_header_no_ID(N,DETERM)
end do
call cpu_time(t2)
t_ID_no_ID = t2 - t1
print*,loop_max
CALL ID_OG_no_ID(N,DETERM)
end do
call cpu_time(t2)
t_OG_no_ID = t2 - t1
print*,loop_max
CALL ID_OG_allocate(N,DETERM)
end do
call cpu_time(t2)
t_allocate = t2 - t1
print*,DETERM
deallocate(ID)
write(1,format_data) N,t_ID_OG,t_allocate
end do
end program test
subroutine ID_OG(N,O-Z)
implicit integer(I-N)
DETERM = 1.0
DO 1 I=1,N
1 ID(I)=0
DETERM = sum(ID)
end subroutine ID_OG
subroutine ID_header(N,only: ID
implicit none
integer,intent(in) :: N
real,intent(out) :: DETERM
integer :: I
DETERM = 1.0
DO 1 I=1,N
1 ID(I)=0
DETERM = sum(ID)
end subroutine ID_header
subroutine ID_header_no_ID(N,DETERM)
implicit none
integer,intent(out) :: DETERM
integer :: I
real,dimension(N) :: ID
DETERM = 1.0
DO 1 I=1,N
1 ID(I)=0
DETERM = sum(ID)
end subroutine ID_header_no_ID
subroutine ID_OG_no_ID(N,DETERM)
implicit real (A-H,O-Z)
implicit integer(I-N)
real,N
1 ID(I)=0
DETERM = sum(ID)
end subroutine ID_OG_no_ID
subroutine ID_OG_allocate(N,allocatable :: ID
allocate(ID(N))
DETERM = 1.0
DO 1 I=1,N
1 ID(I)=0
DETERM = sum(ID)
end subroutine ID_OG_allocate
解决方法
分配数组需要时间。编译器可以自由地在任何需要的地方分配本地数组,但是通常可以通过编译器特定的标志进行调整。将-fstack-arrays
用于gfortran强制将本地数组堆叠。
在堆栈上分配只是更改堆栈指针,实际上是免费的。但是,在堆上进行分配会涉及更多事务,并且需要进行一些记账。
在某些情况下,局部变量按顺序排列,在某些情况下,全局(模块)变量按顺序排列。也可以使用本地保存的变量或某些对象的组成部分的变量。 如果不查看相关代码的完整设计,就无法说出哪个更好。
FWIW,对于-fstack-arrays
,除了使用allocate()
进行显式分配时,我看不出多少区别:
显式allocate
将始终使用堆。
没有-fstack-arrays
,我确实看到了一些东西:
图形非常嘈杂,因为我的笔记本计算机同时运行许多进程。
这并不是说应该总是使用-fstack-arrays
,我曾经演示过这种区别。该选项很有用,但必须注意避免堆栈溢出错误。 -fmax-stack-var-size
可能会有所帮助。
正如您的测试所指出的那样,所有不使用module
变量的方法的额外开销是由于该语言的系统论来避免用户过多地处理内存。
除非您开始修改编译器标志,否则编译器将决定应在哪里分配内存。您认为分配/释放时间是一个缺点,但是您的分析还显示:
-
堆栈与堆内存的处理开销很快变得越来越小:对于
N>=100
,它已经dimension(100)数组是现代计算机上的一个荒谬的小内存块。 -
在模块中声明变量只是为了加快存储速度,这是
Fortran 90
使其成为全局变量的一种方式,因此,这是一种已弃用的编码样式。
我认为使代码和快速编码的最佳策略是:
-
N
在整个运行期间是否保持不变?然后,将其封装到一个类中是一个好主意:
module myCalculation
implicit none
type,public: fancyMethod
integer :: N = 0
real,allocatable :: ID(:)
contains
procedure :: init
procedure :: compute
procedure :: is_init
end type fancyMethod
contains
elemental subroutine init(self,n)
class(fancyMethod),intent(inout) :: self
integer,intent(in) :: n
real,allocatable :: tmp(:)
self%N = n
allocate(tmp(N)); tmp(:) = 0
call move_alloc(from=tmp,to=self%ID)
end subroutine init
elemental logical function is_init(self)
class(fancyMethod),intent(in) :: self
is_init = allocated(self%ID) .and. size(self%ID)>0
end function is_init
real function compute(self,n,...) result(DETERM)
class(fancyMethod),intent(in) :: n
....
if (.not.is_init(self)) call init(self,N)
DETERM = sum(self%ID(1:N))
end function compute
end module myCalculation
-
N
会不变并且很小吗?为什么不只使用PARAMETER
来定义其最大大小?如果它是一个参数,则编译器可能总是将自动数组放在堆栈中:
real function computeWithMaxSize(N) result(DETERM)
integer,intent(in) :: N
integer,parameter :: MAX_SIZE = 1024
real :: ID(MAX_SIZE)
[...]
if (N>MAX_SIZE) stop ' N is too large! '
DETERM = sum(ID(1:N))
end function computeWithMaxSize
-
N
的大小会变大吗?会变大吗?这样,例程内的内存处理就很好了,其开销也可以忽略不计,因为CPU时间将由计算决定。如果不确定大小太大而导致任何堆栈问题,请使用allocatable
版本:
real function computeWithAllocatable(N) result(DETERM)
integer,intent(in) :: N
real,allocatable :: ID(:)
allocate(ID(N))
[...]
DETERM = sum(ID(1:N))
end function computeWithAllocatable