防止重复条件评估逻辑值的最佳方法是什么,这些逻辑值在运行期间不会改变,但必须在运行时指定?
该应用程序是科学计算,它涉及读取一系列输入的大型代码。 然后,代码会使用这些相同的输入值运行数天,数周甚至数月。 其中一些输入是打开某些功能或调整计算方法的标志。 一个例子是:
do i = 1, N
do j = 1, M
!Some calculation
calculated_value = ...
!Flags specify how to use or adjust the calculated_value
if (flag1) then
calculated_value = calculated_value + 1
endif
if (flag2) then
call save_value(calculated_value)
endif
if (flag3) ...
end do
end do
标志必须在循环内部,因为它们打开的功能使用循环中获得的数据。 但是,必须在每个循环中评估标志,随着标志数量的增加,这变得越来越低效。 我考虑的一些可能的解决方案包括:
我记得我听说条件语句通常被假定为它们之前的值,并且只在最后执行检查。 是否可能使用固定标志不是效率问题。 这必须是数值计算中的常见问题,但我无法在谷歌上找到一个好的讨论/解决方案。
编辑:添加代码来计算无标志,参数标志,变量标志和@Alexander Vogt标志,以定义例程选择。
!Module of all permatations of flag conditions
module all_variants
contains
subroutine loop_Flag1_Flag2_Flag3(M,N,a,rand)
implicit none
integer, intent(in) :: M, N
double precision, dimension(:),allocatable, intent(in) :: rand
double precision, intent(inout) :: a
integer :: i,j
#define COND_FLAG1
#define COND_FLAG2
#define COND_FLAG3
#include "common_code.inc.F90"
end subroutine loop_Flag1_Flag2_Flag3
subroutine loop_Flag1_Flag2_nFlag3(M,N,a,rand)
implicit none
integer, intent(in) :: M, N
double precision, dimension(:),allocatable, intent(in) :: rand
double precision, intent(inout) :: a
integer :: i,j
#define COND_FLAG1
#define COND_FLAG2
#ifdef COND_FLAG3
#undef COND_FLAG3
#endif
#include "common_code.inc.F90"
end subroutine loop_Flag1_Flag2_nFlag3
subroutine loop_Flag1_nFlag2_nFlag3(M,N,a,rand)
implicit none
integer, intent(in) :: M, N
double precision, dimension(:),allocatable, intent(in) :: rand
double precision, intent(inout) :: a
integer :: i,j
#define COND_FLAG1
#ifdef COND_FLAG2
#undef COND_FLAG2
#endif
#ifdef COND_FLAG3
#undef COND_FLAG3
#endif
#include "common_code.inc.F90"
end subroutine loop_Flag1_nFlag2_nFlag3
subroutine loop_nFlag1_nFlag2_nFlag3(M,N,a,rand)
implicit none
integer, intent(in) :: M, N
double precision, dimension(:),allocatable, intent(in) :: rand
double precision, intent(inout) :: a
integer :: i,j
#ifdef COND_FLAG1
#undef COND_FLAG1
#endif
#ifdef COND_FLAG2
#undef COND_FLAG2
#endif
#ifdef COND_FLAG3
#undef COND_FLAG3
#endif
#include "common_code.inc.F90"
end subroutine loop_nFlag1_nFlag2_nFlag3
end module all_variants
!Some generic subroutine
subroutine write_a(a)
implicit none
double precision,intent(in) :: a
print*, a
end subroutine write_a
!Main program to time various flag options
program optimise_flags
use all_variants
implicit none
logical :: flag1, flag2, flag3
logical,parameter :: pflag1 = .false., pflag2=.false., pflag3=.false.
integer :: i,j, N,M, rep, repeats
double precision :: a, t1,t2
double precision :: tnf, tpf, tvf, tppf
double precision :: anf, apf, avf, appf
double precision, dimension(:),allocatable :: rand
!Number of runs and zero counters
N = 1000; M = 1000; repeats = 1000
allocate(rand(N*M))
tnf = 0.d0; tpf = 0.d0; tvf = 0.d0; tppf = 0.d0
anf = 0.d0; apf = 0.d0; avf = 0.d0; appf = 0.d0
!Setup variable inputs
open(unit=10,file='./input')
read(10,*) flag1
read(10,*) flag2
read(10,*) flag3
close(unit=10,status='keep')
!Main loop
do rep = 1, repeats
!Generate array of random numbers
!call reset_seed()
call random_number(rand(:))
!vvvvvvv Run with no flags vvvvvv
a = 0.d0
call cpu_time(t1)
do i = 1,N
do j = 1,M
a = a + rand(j+(i-1)*M)
enddo
enddo
call cpu_time(t2)
anf = anf + a
tnf = tnf + t2-t1
!^^^^^^^ Run with no flags ^^^^^^
!vvvvvvv Run with parameter flags vvvvvv
a = 0.d0
call cpu_time(t1)
do i = 1,N
do j = 1,M
a = a + rand(j+(i-1)*M)
if (pflag1) a = a + 1.d0
if (pflag2) call write_a(a)
if (pflag3) a = a**3.d0
enddo
enddo
call cpu_time(t2)
apf = apf + a
tpf = tpf + t2-t1
!^^^^^^^ Run with parameter flags ^^^^^^
!vvvvvvv Run with variable input flags vvvvvvv
a = 0.d0
call cpu_time(t1)
do i = 1,N
do j = 1,M
a = a + rand(j+(i-1)*M)
if (flag1) a = a + 1.d0
if (flag2) call write_a(a)
if (flag3) a = a**3.d0
enddo
enddo
call cpu_time(t2)
avf = avf + a
tvf = tvf + t2-t1
! ^^^^^^ Run with variable input flags ^^^^^^
! vvvvvvv Run with copied subroutines flags vvvvvvv
a = 0.d0
call cpu_time(t1)
!Choose a subroutine using pre-defined flags
if ( flag1 ) then
if ( flag2 ) then
if ( flag3 ) then
call loop_Flag1_Flag2_Flag3(M,N,a,rand)
else
call loop_Flag1_Flag2_nFlag3(M,N,a,rand)
endif
else
call loop_Flag1_nFlag2_nFlag3(M,N,a,rand)
endif
else
call loop_nFlag1_nFlag2_nFlag3(M,N,a,rand)
endif
call cpu_time(t2)
appf = appf + a
tppf = tppf + t2-t1
! ^^^^^^^ Run with copied subroutines flags ^^^^^^^
enddo
print'(4(a,e14.7))', 'Results: for no flag = ', anf, ' Param flag = ', apf, ' Variable flag = ', avf, ' Pre-proc =', appf
print'(4(a,f14.7))', 'Timings: for no flag = ', tnf, ' Param flag = ', tpf, ' Variable flag = ', tvf, ' Pre-proc =', tppf
end program optimise_flags
输入文件包含:
.false.
.false.
.false.
我的时序结果因优化标志和编译器而异,通常如下:
对于ifort -fpp -O3 -xHost -ipo -fast optimise_flags.f90
gfortran -cpp -O3 optimise_flags.f90
结论是使用变量标志会导致性能下降,并且@Alexander Vogt提出的解决方案可以正常工作。
答案 0 :(得分:1)
据我所知,这些标志是的一个问题,特别是如果编译器无法轻易地优化它们。如果性能至关重要,我最好的猜测是分离子程序。下面我将描述如何在没有代码重复的情况下实现该方案。是否加速代码取决于实际代码以及循环和条件的复杂性,因此您需要尝试一下,看看它是否值得付出努力。
您可以使用#include
有效地实现您提到的最后一个选项(单独(但几乎相同)子例程)以避免代码重复:
common_code.inc.F90:
do i = 1, N
do j = 1, M
!Some calculation
calculated_value = ...
!Flags specify how to use or adjust the calculated_value
#ifdef COND_FLAG1
calculated_value = calculated_value + 1
#endif
#ifdef COND_FLAG2
call save_value(calculated_value)
#endif
#ifdef COND_FLAG3
!...
#endif
end do
end do
个别子程序:
module all_variants
contains
subroutine loop_Flag1_nFlag2_nFlag3()
! ...
#define COND_FLAG1
#ifdef COND_FLAG2
#undef COND_FLAG2
#endif
#ifdef COND_FLAG3
#undef COND_FLAG3
#endif
#include "common_code.inc.F90"
end subroutine
! ...
end module
然后你需要处理所有案件:
if ( flag1 ) then
if ( flag2 ) then
if ( flag3 ) then
call loop_Flag1_Flag2_Flag3()
else
call loop_Flag1_Flag2_nFlag3()
endif
else
! ...
endif
else
! ...
endif