Fortran 通过输入文件设置常量标志优化科学代码

Fortran 通过输入文件设置常量标志优化科学代码,fortran,compiler-optimization,intel-fortran,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

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

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

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 optimize_flags.f90

  • 无标志=0.2499380
  • 参数标志=0.2427720
  • 变量标志=0.9796880
  • @Alexander Vogt多个子例程=0.2427100
对于
gfortran-cpp-O3优化标志。f90

  • 无标志=0.8855360
  • 参数标志=0.8882080
  • 变量标志=0.9222320
  • @Alexander Vogt多个子例程=0.8848810

结论是使用可变标志确实会导致性能损失,@Alexander Vogt提出的解决方案是有效的。

据我所知,这些标志是一个问题,特别是如果编译器无法轻松优化它们的话。我的最佳猜测是,如果性能非常关键,则将子例程分开。下面我将勾勒出一个方案,说明如何在不重复代码的情况下实现它。这是否会加快代码的速度取决于实际代码、循环的复杂性和条件,因此您需要尝试一下,看看这是否真的值得

您可以使用
#include
高效地实现您提到的最后一个选项(独立(但几乎相同)子例程),以避免代码重复:

通用代码公司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

一个简单的逻辑评估几乎不需要花费任何时间。您是否对程序计时并得出结论,这些标志确实对性能有影响?如果是这样,那么用预处理器宏替换If条件可能会节省一些时间,并且不会太多地降低可读性。我认为“受保护”是不起作用的,因为变量在循环过程中仍然被间接地允许变化。Hi-Ed-正如Raul所说的第一个概要文件只是为了检查您所担心的是否确实是一个问题。如果每次运行都是预处理,那么只要重新编译不会太痛苦,那么预处理就是一种可行的方法。但是我可以问一下,这些标志是互斥的吗?如果代码运行的时间比编译所需的时间长得多,那么就使用预处理器编译一组特定的标志。谢谢你的评论——我当然看到了我的代码中明显的不同,因为我引入了更多的标志(尽管我使用了许多难以优化的功能,如函数指针、链表、混合语言编程等)。我在问题中添加了一个测试用例和计时结果。感谢您的建议,这是一个很好的解决方案。这是您在实践中使用过的吗?在我看来,这就像fortran的一种模板元编程。它显示了我测试中预期的改进(添加到我的问题中).你知道有没有更优雅的方法来实现这一点,即实际的fortran元编程环境或语言吗?我有一些基于这样的例程作为我的数值例程的核心。不,我不知道有哪个系统可以自动实现这一点。这是微观优化,它的使用非常有限…好的,这个不需要生成多个例程,您可以在开始时使用'if(flag1)定义静态标志然后#define COND_FLAG1 else#ifdef COND_FLAG1#undef COND_FLAG1#endif endif`是的,但是您在编译时定义了标志,并且必须重新编译代码以更改标志…@EdSmith在这种情况下,您只需保留条件并将标志定义为
参数
。然后编译器将优化检查。