使用输入文件设置的常量标志优化科学代码

时间:2015-03-25 12:40:24

标签: fortran compiler-optimization intel-fortran

防止重复条件评估逻辑值的最佳方法是什么,这些逻辑值在运行期间不会改变,但必须在运行时指定?

该应用程序是科学计算,它涉及读取一系列输入的大型代码。 然后,代码会使用这些相同的输入值运行数天,数周甚至数月。 其中一些输入是打开某些功能或调整计算方法的标志。 一个例子是:

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保护模块为编译器提供提示,这些值不会发生变化(这有效吗?)。
  • 使用函数指针或对象来改变每次计算的内容。
  • 每个标志组合完全分开(但几乎相同)子程序。

我记得我听说条件语句通常被假定为它们之前的值,并且只在最后执行检查。 是否可能使用固定标志不是效率问题。 这必须是数值计算中的常见问题,但我无法在谷歌上找到一个好的讨论/解决方案。

编辑:添加代码来计算无标志,参数标志,变量标志和@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

  • no flag = 0.2499380
  • Param flag = 0.2427720
  • 变量标志= 0.9796880
  • @Alexander Vogt multi-subroutines = 0.2427100

gfortran -cpp -O3 optimise_flags.f90

  • no flag = 0.8855360
  • Param flag = 0.8882080
  • 变量标志= 0.9222320
  • @Alexander Vogt multi-subroutines = 0.8848810

结论是使用变量标志会导致性能下降,并且@Alexander Vogt提出的解决方案可以正常工作。

1 个答案:

答案 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