Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance 回路优化_Performance_Caching_Optimization_Fortran_Hpc - Fatal编程技术网

Performance 回路优化

Performance 回路优化,performance,caching,optimization,fortran,hpc,Performance,Caching,Optimization,Fortran,Hpc,我正试图了解在源代码中可以做哪些缓存或其他优化来加快循环。我认为它对缓存非常友好,但是,有没有专家可以在调优这段代码时挤出更多的性能呢 DO K = 1, NZ DO J = 1, NY DO I = 1, NX SIDEBACK = STEN(I-1,J-1,K-1) + STEN(I-1,J,K-1) + STEN(I-1,J+1,K-1) + & STEN(I ,J-1,K-

我正试图了解在源代码中可以做哪些缓存或其他优化来加快循环。我认为它对缓存非常友好,但是,有没有专家可以在调优这段代码时挤出更多的性能呢

DO K = 1, NZ 
    DO J = 1, NY
        DO I = 1, NX

             SIDEBACK  = STEN(I-1,J-1,K-1) + STEN(I-1,J,K-1) + STEN(I-1,J+1,K-1) + &
                         STEN(I  ,J-1,K-1) + STEN(I  ,J,K-1) + STEN(I  ,J+1,K-1) + &
                         STEN(I+1,J-1,K-1) + STEN(I+1,J,K-1) + STEN(I+1,J+1,K-1) 

             SIDEOWN   = STEN(I-1,J-1,K)   + STEN(I-1,J,K)   + STEN(I-1,J+1,K) + &
                         STEN(I  ,J-1,K)   + STEN(I  ,J,K)   + STEN(I  ,J+1,K) + &
                         STEN(I+1,J-1,K)   + STEN(I+1,J,K)   + STEN(I+1,J+1,K)

             SIDEFRONT = STEN(I-1,J-1,K+1) + STEN(I-1,J,K+1) + STEN(I-1,J+1,K+1) + &
                         STEN(I  ,J-1,K+1) + STEN(I  ,J,K+1) + STEN(I  ,J+1,K+1) + &
                         STEN(I+1,J-1,K+1) + STEN(I+1,J,K+1) + STEN(I+1,J+1,K+1)

             RES(I,J,K) = ( SIDEBACK + SIDEOWN + SIDEFRONT ) / 27.0

        END DO
    END DO
END DO

好的,我想我已经尽了我所能,不幸的是我的结论是没有太多的优化空间,除非你愿意进入并行化。让我们看看为什么,让我们看看你能做什么和不能做什么

编译器优化 如今的编译器非常擅长优化代码,远远超过人类。依靠编译器所做的优化还有一个额外的好处,即它们不会破坏源代码的可读性。无论您做什么,(在优化速度时)总是尝试使用编译器标志的每一个合理组合。您甚至可以尝试多个编译器。就我个人而言,我只使用了gfortran(包含在GCC中)(操作系统是64位Windows),我相信它具有高效和正确的优化技术

-O2
几乎总能大幅提高速度,但即使是
-O3
也是安全的赌注(其中包括美味的循环展开)。对于这个问题,我还尝试了
-ffast math
-feexpensive优化
,它们没有任何可测量的效果,但是
-march-corei7
(特定于核心i7的cpu体系结构调整),所以我使用
-O3-march-corei7

那么它到底有多快呢? 我编写了以下代码来测试您的解决方案,并使用
-O3-march-corei7
对其进行了编译。通常在0.78-0.82秒之间

program benchmark
implicit none

real :: start, finish
integer :: I, J, K
real :: SIDEBACK, SIDEOWN, SIDEFRONT

integer, parameter :: NX = 600
integer, parameter :: NY = 600
integer, parameter :: NZ = 600
real, dimension (0 : NX + 2, 0 : NY + 2, 0 : NZ + 2) :: STEN
real, dimension (0 : NX + 2, 0 : NY + 2, 0 : NZ + 2) :: RES
call random_number(STEN)

call cpu_time(start)
DO K = 1, NZ
    DO J = 1, NY
        DO I = 1, NX

            SIDEBACK =  STEN(I-1,J-1,K-1) + STEN(I-1,J,K-1) + STEN(I-1,J+1,K-1) + &
                        STEN(I  ,J-1,K-1) + STEN(I  ,J,K-1) + STEN(I  ,J+1,K-1) + &
                        STEN(I+1,J-1,K-1) + STEN(I+1,J,K-1) + STEN(I+1,J+1,K-1)

            SIDEOWN =   STEN(I-1,J-1,K)   + STEN(I-1,J,K)   + STEN(I-1,J+1,K) + &
                        STEN(I  ,J-1,K)   + STEN(I  ,J,K)   + STEN(I  ,J+1,K) + &
                        STEN(I+1,J-1,K)   + STEN(I+1,J,K)   + STEN(I+1,J+1,K)

            SIDEFRONT = STEN(I-1,J-1,K+1) + STEN(I-1,J,K+1) + STEN(I-1,J+1,K+1) + &
                        STEN(I  ,J-1,K+1) + STEN(I  ,J,K+1) + STEN(I  ,J+1,K+1) + &
                        STEN(I+1,J-1,K+1) + STEN(I+1,J,K+1) + STEN(I+1,J+1,K+1)

            RES(I,J,K) = ( SIDEBACK + SIDEOWN + SIDEFRONT ) / 27.0

        END DO
    END DO
END DO
call cpu_time(finish)
!Use the calculated value, so the compiler doesn't optimize away everything.
!Print the original value as well, because one can never be too paranoid.
print *, STEN(1,1,1), RES(1,1,1)
print '(f6.3," seconds.")',finish-start

end program
好的,这就是编译器所能给我们的。下一步是什么

存储中间结果? 正如你可能从问号中怀疑的那样,这一个并没有真正起作用。很抱歉但我们不要急着这么做。 如评论中所述,您当前的代码多次计算每个部分和,这意味着一次迭代的
STEN(I+1,J-1,K-1)+STEN(I+1,J,K-1)+STEN(I+1,J+1,K-1)
将是下一次迭代的
STEN(I,J-1,K-1)+STEN(I,J,K-1)
,因此无需再次获取和计算,您可以存储这些部分结果。 问题是,我们不能存储太多的部分结果。正如您所说,您的代码已经对缓存非常友好,存储的每个部分和意味着可以在一级缓存中少存储一个数组元素。我们可以从
I
的最后几次迭代中存储几个值(索引
I-2
I-3
等的值),但编译器几乎肯定已经这样做了。我有两个证据证明这一怀疑。首先,我的手动循环展开使程序变慢了,大约5%

DO K = 1, NZ
    DO J = 1, NY
        DO I = 1, NX, 8

            SIDEBACK(0) = STEN(I-1,J-1,K-1) + STEN(I-1,J,K-1) + STEN(I-1,J+1,K-1)
            SIDEBACK(1) = STEN(I  ,J-1,K-1) + STEN(I  ,J,K-1) + STEN(I  ,J+1,K-1)
            SIDEBACK(2) = STEN(I+1,J-1,K-1) + STEN(I+1,J,K-1) + STEN(I+1,J+1,K-1)
            SIDEBACK(3) = STEN(I+2,J-1,K-1) + STEN(I+2,J,K-1) + STEN(I+2,J+1,K-1)
            SIDEBACK(4) = STEN(I+3,J-1,K-1) + STEN(I+3,J,K-1) + STEN(I+3,J+1,K-1)
            SIDEBACK(5) = STEN(I+4,J-1,K-1) + STEN(I+4,J,K-1) + STEN(I+4,J+1,K-1)
            SIDEBACK(6) = STEN(I+5,J-1,K-1) + STEN(I+5,J,K-1) + STEN(I+5,J+1,K-1)
            SIDEBACK(7) = STEN(I+6,J-1,K-1) + STEN(I+6,J,K-1) + STEN(I+6,J+1,K-1)
            SIDEBACK(8) = STEN(I+7,J-1,K-1) + STEN(I+7,J,K-1) + STEN(I+7,J+1,K-1)
            SIDEBACK(9) = STEN(I+8,J-1,K-1) + STEN(I+8,J,K-1) + STEN(I+8,J+1,K-1)

            SIDEOWN(0) = STEN(I-1,J-1,K) + STEN(I-1,J,K) + STEN(I-1,J+1,K)
            SIDEOWN(1) = STEN(I  ,J-1,K) + STEN(I  ,J,K) + STEN(I  ,J+1,K)
            SIDEOWN(2) = STEN(I+1,J-1,K) + STEN(I+1,J,K) + STEN(I+1,J+1,K)
            SIDEOWN(3) = STEN(I+2,J-1,K) + STEN(I+2,J,K) + STEN(I+2,J+1,K)
            SIDEOWN(4) = STEN(I+3,J-1,K) + STEN(I+3,J,K) + STEN(I+3,J+1,K)
            SIDEOWN(5) = STEN(I+4,J-1,K) + STEN(I+4,J,K) + STEN(I+4,J+1,K)
            SIDEOWN(6) = STEN(I+5,J-1,K) + STEN(I+5,J,K) + STEN(I+5,J+1,K)
            SIDEOWN(7) = STEN(I+6,J-1,K) + STEN(I+6,J,K) + STEN(I+6,J+1,K)
            SIDEOWN(8) = STEN(I+7,J-1,K) + STEN(I+7,J,K) + STEN(I+7,J+1,K)
            SIDEOWN(9) = STEN(I+8,J-1,K) + STEN(I+8,J,K) + STEN(I+8,J+1,K)

            SIDEFRONT(0) = STEN(I-1,J-1,K+1) + STEN(I-1,J,K+1) + STEN(I-1,J+1,K+1)
            SIDEFRONT(1) = STEN(I  ,J-1,K+1) + STEN(I  ,J,K+1) + STEN(I  ,J+1,K+1)
            SIDEFRONT(2) = STEN(I+1,J-1,K+1) + STEN(I+1,J,K+1) + STEN(I+1,J+1,K+1)
            SIDEFRONT(3) = STEN(I+2,J-1,K+1) + STEN(I+2,J,K+1) + STEN(I+2,J+1,K+1)
            SIDEFRONT(4) = STEN(I+3,J-1,K+1) + STEN(I+3,J,K+1) + STEN(I+3,J+1,K+1)
            SIDEFRONT(5) = STEN(I+4,J-1,K+1) + STEN(I+4,J,K+1) + STEN(I+4,J+1,K+1)
            SIDEFRONT(6) = STEN(I+5,J-1,K+1) + STEN(I+5,J,K+1) + STEN(I+5,J+1,K+1)
            SIDEFRONT(7) = STEN(I+6,J-1,K+1) + STEN(I+6,J,K+1) + STEN(I+6,J+1,K+1)
            SIDEFRONT(8) = STEN(I+7,J-1,K+1) + STEN(I+7,J,K+1) + STEN(I+7,J+1,K+1)
            SIDEFRONT(9) = STEN(I+8,J-1,K+1) + STEN(I+8,J,K+1) + STEN(I+8,J+1,K+1)

            RES(I    ,J,K) = (  SIDEBACK(0) + SIDEOWN(0) + SIDEFRONT(0) + &
                                SIDEBACK(1) + SIDEOWN(1) + SIDEFRONT(1) + &
                                SIDEBACK(2) + SIDEOWN(2) + SIDEFRONT(2) ) / 27.0
            RES(I + 1,J,K) = (  SIDEBACK(1) + SIDEOWN(1) + SIDEFRONT(1) + &
                                SIDEBACK(2) + SIDEOWN(2) + SIDEFRONT(2) + &
                                SIDEBACK(3) + SIDEOWN(3) + SIDEFRONT(3) ) / 27.0
            RES(I + 2,J,K) = (  SIDEBACK(2) + SIDEOWN(2) + SIDEFRONT(2) + &
                                SIDEBACK(3) + SIDEOWN(3) + SIDEFRONT(3) + &
                                SIDEBACK(4) + SIDEOWN(4) + SIDEFRONT(4) ) / 27.0
            RES(I + 3,J,K) = (  SIDEBACK(3) + SIDEOWN(3) + SIDEFRONT(3) + &
                                SIDEBACK(4) + SIDEOWN(4) + SIDEFRONT(4) + &
                                SIDEBACK(5) + SIDEOWN(5) + SIDEFRONT(5) ) / 27.0
            RES(I + 4,J,K) = (  SIDEBACK(4) + SIDEOWN(4) + SIDEFRONT(4) + &
                                SIDEBACK(5) + SIDEOWN(5) + SIDEFRONT(5) + &
                                SIDEBACK(6) + SIDEOWN(6) + SIDEFRONT(6)   ) / 27.0
            RES(I + 5,J,K) = (  SIDEBACK(5) + SIDEOWN(5) + SIDEFRONT(5) + &
                                SIDEBACK(6) + SIDEOWN(6) + SIDEFRONT(6) + &
                                SIDEBACK(7) + SIDEOWN(7) + SIDEFRONT(7) ) / 27.0
            RES(I + 6,J,K) = (  SIDEBACK(6) + SIDEOWN(6) + SIDEFRONT(6) + &
                                SIDEBACK(7) + SIDEOWN(7) + SIDEFRONT(7) + &
                                SIDEBACK(8) + SIDEOWN(8) + SIDEFRONT(8) ) / 27.0
            RES(I + 7,J,K) = ( SIDEBACK(7) + SIDEOWN(7) + SIDEFRONT(7) + &
                                SIDEBACK(8) + SIDEOWN(8) + SIDEFRONT(8) + &
                                SIDEBACK(9) + SIDEOWN(9) + SIDEFRONT(9) ) / 27.0

        END DO
    END DO
END DO
更糟糕的是,很容易证明我们已经非常接近理论上可能的最小执行时间。为了计算所有这些平均值,我们需要做的绝对最小值是至少访问每个元素一次,然后将它们除以27.0。因此,您永远不会比下面的代码更快,它在我的机器上的执行时间不到0.48-0.5秒

program benchmark
implicit none

real :: start, finish
integer :: I, J, K

integer, parameter :: NX = 600
integer, parameter :: NY = 600
integer, parameter :: NZ = 600
real, dimension (0 : NX + 2, 0 : NY + 2, 0 : NZ + 2) :: STEN
real, dimension (0 : NX + 2, 0 : NY + 2, 0 : NZ + 2) :: RES
call random_number(STEN)

call cpu_time(start)
DO K = 1, NZ
    DO J = 1, NY
        DO I = 1, NX

            !This of course does not do what you want to do,
            !this is just an example of a speed limit we can never surpass.
            RES(I, J, K) = STEN(I, J, K) / 27.0

        END DO
    END DO
END DO
call cpu_time(finish)
!Use the calculated value, so the compiler doesn't optimize away everything.
print *, STEN(1,1,1), RES(1,1,1)
print '(f6.3," seconds.")',finish-start

end program
但是,即使是消极的结果也是结果。如果仅仅访问每个元素一次(除以27.0)就占用了一半以上的执行时间,那就意味着内存访问是瓶颈。然后,也许您可以优化

更少的数据 如果不需要64位双精度的完整精度,可以使用
real(kind=4)
类型声明数组。但也许你的real已经是4字节了。在这种情况下,我相信一些Fortran实现支持非标准的16位双精度,或者根据您的数据,您可以只使用整数(可能是浮点乘以一个数字,然后四舍五入为整数)。基类型越小,可以放入缓存的元素就越多。最理想的是
integer(kind=1)
,当然,与
real(kind=4)
相比,它在我的机器上的速度提高了2倍多。但这取决于你需要的精度

更好的位置 当需要来自相邻列的数据时,列主数组的速度较慢,而行主数组对于相邻行的速度较慢。 幸运的是,有一种称为a的时髦的数据存储方式,它在计算机图形学中确实有。 我不能保证这会有帮助,也许会适得其反,但也许不会。对不起,老实说,我不想自己实现它

并行化
说到计算机图形学,这个问题非常简单,甚至可以在GPU上进行很好的并行处理,但是如果你不想走那么远,你可以使用一个普通的多核CPU。似乎是一个搜索Fortran并行化库的好地方。

请确认,这是Fortran,而STEN是一个列主数组?@szmate1618更正我认为您可以做得更多。当前代码多次计算每个部分和。我的意思是一次迭代的
STEN(I+1,J-1,K-1)+STEN(I+1,J,K-1)+STEN(I+1,J+1,K-1)
将是下一次迭代的
STEN(I,J-1,K-1)+STEN(I,J,K-1)
,因此无需再次获取和计算,您可以存储这些部分结果。我已经在做一个完整的实现,很快就会发布。等等,我很笨。如果Fortran编译器能够展开循环,那么它可能能够执行与我尝试执行的相同的优化。如果将STEN作为输入参数传递,它可以很容易地做到这一点。Fortran以没有别名、极端循环展开而闻名。这些都不是指针,对吗?
real(kind=4)
不必是4字节,它可能根本不存在,事实上在某些编译器中它并不存在。类似地,
real(kind=1)
确实存在于某些编译器中,但它可能是一个常见的默认实数。展开可能无效,因为您没有使用parens或其他方法来确保编译器识别相邻赋值之间的公共子表达式。