使用输入文件设置的常量标志优化科学代码
Optimisation of scientific code with constant flags set by input file
防止在 运行 期间不更改但必须在 运行 时间指定的逻辑值重复条件评估的最佳方法是什么?
该应用程序是科学计算,涉及读取一系列输入的大型代码。
然后代码 运行s 使用这些相同的输入值持续数天、数周甚至数月。
其中一些输入是开启某些功能或调整计算方法的标志。
一个例子是:
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
标志必须在循环内,因为它们打开的功能使用在循环内获得的数据。
但是,必须在每个循环中评估标志,并且随着标志数量的增加,效率会越来越低。
我正在考虑的一些可能的解决方案包括:
- 解析输入文件(例如使用 python/bash),生成一个参数文件并将它们包含在编译代码中。
- 配置文件引导的编译器优化(尽管根据我的经验,这通常比积极的静态标志表现更差)。
- 用于向编译器提供提示的 Fortran 保护模块,这些值不会改变(这行得通吗?)。
- 一些函数指针或对象的使用改变了每次计算的内容。
- 每个标志组合的子程序完全独立(但几乎相同)。
我记得听说条件语句通常被假定为其先前的值,并且仅在最后执行检查。
在这种情况下,也许使用固定标志与效率无关。
这一定是数值计算中的一个常见问题,但我在 google.
上找不到好的 discussion/solution
编辑:添加代码来计时无标志、参数标志、变量标志和@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
- 无标志 = 0.2499380
- 参数标志 = 0.2427720
- 变量标志 = 0.9796880
- @Alexander Vogt 多子程序 = 0.2427100
对于gfortran -cpp -O3 optimise_flags.f90
- 无标志 = 0.8855360
- 参数标志 = 0.8882080
- 变量标志 = 0.9222320
- @Alexander Vogt 多子程序 = 0.8848810
结论是使用变量标志确实会导致性能下降,@Alexander Vogt 提出的解决方案有效。
据我所知,这些标志是一个问题,尤其是当编译器不能轻松优化它们时。如果性能至关重要,我最好的猜测是将子程序分开。下面我勾画了一个方案,你可以如何在没有代码重复的情况下实现它。这是否会加速您的代码取决于实际代码以及循环和条件的复杂性,因此您需要尝试一下看看它是否真的值得付出努力。
您可以使用 #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
防止在 运行 期间不更改但必须在 运行 时间指定的逻辑值重复条件评估的最佳方法是什么?
该应用程序是科学计算,涉及读取一系列输入的大型代码。 然后代码 运行s 使用这些相同的输入值持续数天、数周甚至数月。 其中一些输入是开启某些功能或调整计算方法的标志。 一个例子是:
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
标志必须在循环内,因为它们打开的功能使用在循环内获得的数据。 但是,必须在每个循环中评估标志,并且随着标志数量的增加,效率会越来越低。 我正在考虑的一些可能的解决方案包括:
- 解析输入文件(例如使用 python/bash),生成一个参数文件并将它们包含在编译代码中。
- 配置文件引导的编译器优化(尽管根据我的经验,这通常比积极的静态标志表现更差)。
- 用于向编译器提供提示的 Fortran 保护模块,这些值不会改变(这行得通吗?)。
- 一些函数指针或对象的使用改变了每次计算的内容。
- 每个标志组合的子程序完全独立(但几乎相同)。
我记得听说条件语句通常被假定为其先前的值,并且仅在最后执行检查。 在这种情况下,也许使用固定标志与效率无关。 这一定是数值计算中的一个常见问题,但我在 google.
上找不到好的 discussion/solution编辑:添加代码来计时无标志、参数标志、变量标志和@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
- 无标志 = 0.2499380
- 参数标志 = 0.2427720
- 变量标志 = 0.9796880
- @Alexander Vogt 多子程序 = 0.2427100
对于gfortran -cpp -O3 optimise_flags.f90
- 无标志 = 0.8855360
- 参数标志 = 0.8882080
- 变量标志 = 0.9222320
- @Alexander Vogt 多子程序 = 0.8848810
结论是使用变量标志确实会导致性能下降,@Alexander Vogt 提出的解决方案有效。
据我所知,这些标志是一个问题,尤其是当编译器不能轻松优化它们时。如果性能至关重要,我最好的猜测是将子程序分开。下面我勾画了一个方案,你可以如何在没有代码重复的情况下实现它。这是否会加速您的代码取决于实际代码以及循环和条件的复杂性,因此您需要尝试一下看看它是否真的值得付出努力。
您可以使用 #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