【问题标题】:Running loops only for specified sets of integers in fortran仅针对 fortran 中指定的整数集运行循环
【发布时间】:2020-02-13 09:39:31
【问题描述】:
        do i=1,n
                s=0
                do l=1,n
                do m=1,n


                    s=s-a(i,l,m)*q0(l)*q0(m)

                end do
                end do


                f0(i)=s-g(i)*q0(i)
        end do

这是我的代码的一部分。由于我必须同时运行三个循环,因此整体执行变得非常缓慢。

重要的事实是,在这里,数组 a(i,l,m) 仅对于 a(l,m,n) 的一组值是非零的。下面是设置a(i,l,m)的代码。

do i=1,n
    do l=1,n
        do m=1,n

        if(i.eq.l+m .or. i.eq.-l+m .or. i.eq.l-m) then
        a1=1
        else 
        a1=0
        end if



        if(i+l+m.eq.n+n+2 .or. i-l+m.eq.n+n+2 .or. i+l-m.eq.n+n+2 .or. i-l-m.eq.n+n+2) then
        b1=1
        else
        b1=0
        end if

    a(i,l,m)=(a1-b1)    (!multiplied with some long function, erased for ease of understanding)

end do
    end do
        end do

现在,fortran 中是否有任何方法仅针对 a(i,l,m) 非零的 (i,l,m) 的值运行循环?(非零集仅在仅计算,可以看出)这将节省大量时间。

【问题讨论】:

  • 有没有先验的方法来知道哪个索引是非零的?如果不是,则需要一个 if 条件。
  • 把它写成在 l&m 上的循环。对于每个条件,依次起作用 i 的哪些值给出非零贡献并应用它。没有 if 条件和 O(n^2) 除非我错过了什么
  • 请注意,在 Fortran 中,数组的第一个索引是运行速度最快的索引,因此为了提高速度(更好地使用缓存),重新排序循环也可能很有用。

标签: loops optimization fortran compiler-optimization gfortran


【解决方案1】:

您不需要存储所有 a 数组,您可以在需要时计算其元素,并使用守卫(额外的数组元素以避免超出范围的索引)来避免 if 条件。以下是您可以这样做的方法,将问题减少到 O(n^2) 并使用更少的内存。另请注意,我提供了一个完整的测试程序,这使得回答问题变得更加容易 - 请以后自己做!

ijb@ianbushdesktop ~/work/stack $ cat o3.f90
Program o3

  Implicit None

  Integer, Parameter :: wp = Selected_real_kind( 12, 70 )

  Real( wp ), Dimension( :, :, : ), Allocatable :: a

  Real( wp ), Dimension( : ), Allocatable :: q0, g, f0, s2

  Real( wp ) :: a1, b1
  Real( wp ) :: s

  Integer :: n
  Integer :: start, finish, rate
  Integer :: i, l, m

  Write( *, * ) 'n ?'
  Read ( *, * ) n 

  Allocate( a( 1:n, 1:n, 1:n ) )
  Allocate( q0( 1:n ) )
  Allocate( f0( 1:n ) )
  Allocate(  g( 1:n ) )
  Allocate( s2( -4 * n - 2:4 * n + 2 ) ) ! guards to avoid out of bounds - haven't thought very carefully about
                                         ! what they should be!!

  Call Random_number( q0 )
  Call Random_number( g )

  Call system_clock( start , rate )
  b1 = 0.0_wp
  do i=1,n
     do l=1,n
        do m=1,n

           if(i.eq.l+m .or. i.eq.-l+m .or. i.eq.l-m) then
              a1=1.0_wp
           else 
              a1=0.0_wp
           end if

           if(i+l+m.eq.n+n+2 .or. i-l+m.eq.n+n+2 .or. i+l-m.eq.n+n+2 .or. i-l-m.eq.n+n+2) then
              b1=1.0_wp
           else
              b1=0.0_wp
           end if

           a(i,l,m)=(a1-b1)    !(multiplied with some long function, erased for ease of understanding)

        end do
     end do
  end do
  do i=1,n
     s=0.0_wp
     do l=1,n
        do m=1,n

           s=s-a(i,l,m)*q0(l)*q0(m)

        end do
     end do
     f0(i)=s-g(i)*q0(i)
  end do
  Call system_clock( finish, rate )
  Write( *, * ) 'Sum f0, time: ', Sum( f0 ), Real( finish - start ) / rate

  Call system_clock( start , rate )
  s2 = 0.0_wp
  Do l = 1, n
     Do m = 1, n

        ! First condition
        i = l + m
        a1 = 1.0_wp
        s2( i ) = s2( i ) - a1 * q0( l ) * q0( m )

        ! Second condition
        i = - l + m
        a1 = 1.0_wp
        s2( i ) = s2( i ) - a1 * q0( l ) * q0( m )

        ! Third condition
        i = l - m
        a1 = 1.0_wp
        s2( i ) = s2( i ) - a1 * q0( l ) * q0( m )

        ! Fourth Condition
        i = 2 * n + 2 - l - m
        b1 = 1.0_wp
        s2( i ) = s2( i ) - ( - b1 ) * q0( l ) * q0( m )

        ! Fifth Condition
        i = 2 * n + 2 + l - m
        b1 = 1.0_wp
        s2( i ) = s2( i ) - ( - b1 ) * q0( l ) * q0( m )


        ! Sixth Condition
        i = 2 * n + 2 - l + m
        b1 = 1.0_wp
        s2( i ) = s2( i ) - ( - b1 ) * q0( l ) * q0( m )

        ! Seventh Condition
        i = 2 * n + 2 + l + m
        b1 = 1.0_wp
        s2( i ) = s2( i ) - ( - b1 ) * q0( l ) * q0( m )


     End Do
  End Do
  Do i = 1, n
     f0( i ) = s2( i ) - g( i ) * q0( i )
  End Do
  Call system_clock( finish, rate )
  Write( *, * ) 'Sum f0, time: ', Sum( f0 ), Real( finish - start ) / rate
End Program o3
ijb@ianbushdesktop ~/work/stack $ gfortran --version
GNU Fortran (GCC) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ijb@ianbushdesktop ~/work/stack $ gfortran -Wall -Wextra -std=f2008 -fcheck=all -O o3.f90
ijb@ianbushdesktop ~/work/stack $ ./a.out
 n ?
300
 Sum f0, time:   -23660.711846511185       0.446999997    
 Sum f0, time:   -23660.711846511185        1.00000005E-03
ijb@ianbushdesktop ~/work/stack $ gfortran -Wall -Wextra -std=f2008 -O3 o3.f90
ijb@ianbushdesktop ~/work/stack $ ./a.out
 n ?
300
 Sum f0, time:   -21932.467299817898       0.298999995    
 Sum f0, time:   -21932.467299817898        0.00000000    
ijb@ianbushdesktop ~/work/stack $ ./a.out
 n ?
1000
 Sum f0, time:   -238036.00437753636        52.4760017    
 Sum f0, time:   -238036.00437753636        2.00000009E-03
ijb@ianbushdesktop ~/work/stack $ 

【讨论】:

  • 请注意i 是最大的n 和最小的0,在这种情况下它可以是2n
【解决方案2】:

Fortran 和许多其他编程语言类似,都有跳转语句,允许人们在标准循环控制之外操纵循环迭代。在 Fortran 中,这些语句是 CYCLE 和 EXIT:

CYCLE 语句:可以通过执行属于构造的 CYCLE 语句来减少循环迭代的执行
EXIT 语句: EXIT 语句提供了一种方法终止循环,或完成另一个构造的执行。

当特定索引与计算无关时,现在可以使用这些构造快速循环循环。在 OP 的情况下,可以执行以下操作:

do i=1,n
   s=0
   do l=1,n
      do m=1,n
         if (a(i,l,m) == 0) cycle
         s=s-a(i,l,m)*q0(l)*q0(m)
      end do
   end do
   f0(i)=s-g(i)*q0(i)
end do

当然,您应该始终考虑到这仍然是一个 O(n^3) 问题。

但是,关于如何构造 3d 数组 a 的更多信息。由于a(i,l,m) = a1 - b1a1b1 根据条件只能具有值01,因此如果仅满足其中一个条件,则元素a(i,l,m) 不为0。现在很容易检查是否满足第一个条件:

i == l+m .or. i == -l+m .or. i == l-m

第二个条件从不满足:

i+l+m == 2*n+2 .or. i-l+m == 2*n+2 .or. i+l-m == 2*n+2 .or. i-l-m == 2*n+2

所以只能同时满足一个条件。这为您提供了一些额外的杠杆作用来加快速度并消除内部循环,使 O(n^2):

do i=1,n
  s=0
  do l=1,n
     m=i-l
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
     m=i+l
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
     m=l-i
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
     m=2*n+2-i-l
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
     m=2*n+2-i+l
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
     m=-(2*n+2-i-l)
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
     m=-(2*n+2-i+l)
     if (m > 0 .and. m <= n) s=s-a(i,l,m)*q0(l)*q0(m)
  end do
  f0(i)=s-g(i)*q0(i)
end do

肯定还有进一步改进的可能。

【讨论】:

  • 您能否详细说明进一步的改进,正如您在最后一行所说的。您的建议使代码更快,但更快会有所帮助。
  • 这是我们需要对q0 有更多了解的地方(它是数组还是函数?)此外,您可以进一步清理 if 条件。例如,如果您执行了m=i-l,则您无需再对m=l-i 执行任何操作。这里还存在重复计算的问题。如果i=l,有很多重复计算正在进行,我在现有部分中忘记了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-06
  • 1970-01-01
  • 2018-11-21
相关资源
最近更新 更多