我有一个包含数千个双精度实数的网格。
它正在迭代,我需要它在达到收敛到3位小数时停止。
目标是让它尽可能快地运行,但每次都需要给出相同的结果(至3 dp)。
我正在做这样的事情
REAL(KIND=DP) :: TOL = 0.001_DP
DO WHILE(.NOT. CONVERGED)
CONVERGED = .TRUE.
DO I = 1, NUM_POINTS
NEW POTENTIAL = !blah blah blah
IF (CONVERGED) THEN
IF (NEW_POTENTIAL < OLD_POTENTIAL - TOL .OR. NEW_POTENTIAL > OLD_POTENTIAL + TOL) THEN
CONVERGED = .FALSE.
END IF
END IF
OLD_POTENTIAL = NEW POTENTIAL
END DO
END DO
我认为许多IF语句对于性能来说不是太大。我想到最后检查收敛情况;找到平均值(将整个网格求和,除以num_points),并检查它是否以与上述相同的方式收敛,但我不相信这将始终是准确的。
这样做的最佳方式是什么?
答案 0 :(得分:3)
如果我理解正确,您可以进行某种时间步骤,通过new_potential
上的计算在old_potential
中创建值。然后把旧的等于新的继续。
您可以使用单个语句
替换现有的收敛测试converged = all(abs(new_potential - old_potential)<tol)
可能更快。如果测试的速度是一个主要问题,你可以只测试每一次(或每三或四次......)迭代
一些评论:
1)如果您使用了具有2个平面的潜在数组,而不是old_和new_potential,则可以通过在每次迭代结束时交换索引将new_转换为old_。正如你的代码所代表的那样,正在进行大量的数据移动。
2)虽然在语义上你有一个while循环是正确的,但我总是使用一个迭代次数最多的do循环,以防万一从未满足收敛标准。
3)在你的声明REAL(KIND=DP) :: TOL = 0.001_DP
中,关于TOL数值的DP规范是多余的,REAL(KIND=DP) :: TOL = 0.001
就足够了。我也将它作为一个参数,如果编译器知道它是不可变的,它可能能够优化它的使用。
4)你真的不需要在最外面的循环中执行CONVERGED = .TRUE.
,在第一次迭代之前设置它 - 这将为你节省一两纳秒。
最后,如果您的收敛标准是潜在数组中的每个元素都已收敛到3dp,那么您应该测试它。根据建议的平均值构建反例会相对容易。但是,我担心的是你的系统永远不会收敛于每个元素,你应该使用一些矩阵范数计算来确定收敛。在这个主题上,SO不适合上课。
答案 1 :(得分:0)
收敛标准的计算是什么?除非它们更糟,然后计算以推进潜力,最好让IF语句尽快终止循环,而不是猜测大量的迭代以确保获得一个好的解决方案。
Re High Performance Mark的建议#1,如果复制操作是运行时间的重要部分,你也可以使用指针。
确保这些东西的唯一方法是测量运行时间...... Fortran提供了测量CPU和时钟时间的内在函数。否则,您可能会修改您的部分代码以使其更快,也许使其更容易理解并可能引入错误,可能在运行时没有太大改进...如果该部分占用总运行时的少量,没有多少聪明才会有很大的不同。
正如High Performance Mark所说,尽管当前的语义很优雅,但您可能希望防范无限循环。一种方法:
PotentialLoop: do i=1, MaxIter
blah
Converged = test...
if (Converged) exit PotentialLoop
blah
end do PotentialLoop
if (.NOT. Converged) write (*, *) "error, did not converge"
答案 2 :(得分:0)
I = 1
DO
NEWPOT = !bla bla bla
IF (ABS(NEWPOT-OLDPOT).LT.TOL) EXIT
OLDPOT = NEWPOT
I = MOD(I,NUMPOINTS) + 1
END DO
也许更好
I = 1
DO
NEWPOT = !bla bla bla
IF (ABS(NEWPOT-OLDPOT).LT.TOL) EXIT
OLDPOT = NEWPOT
IF (I.EQ.NUMPOINTS) THEN
I = 1
ELSE
I = I + 1
END IF
END DO