【发布时间】:2022-11-29 19:27:00
【问题描述】:
背景
我正在使用 Fortran90 和 OpenMP 模拟分子动力学中 N 个带电粒子的运动。施加到每个离子 i 的力的解析表达式是已知的,并且是离子 i 和其他离子 (r_x、r_y、r_z) 位置的函数。我使用并行的 2 嵌套 do 循环计算每对离子之间的库仑相互作用。我可以确定循环结束时每个离子的加速度(a2_x、a2_y、a2_z)(然后用 velocity-Verlet 更新速度和位置)。
方法
我在我的程序中使用以下代码来计算应用于每个离子的库仑力。我计算下一个时间步的加速度 (a2_x),从当前时间步的位置 (r_x) 开始。这是一个 3D 问题,我写了所有的行,但其中大部分对于 x、y 和 z 都是一样的,所以在第一次阅读时你可以只考虑 _x 变量来了解它是如何工作的。
我在 C 线程上并行化我的循环,ia 和 ib 是用于将 N 离子分成 C 部分的数组。例如C=4线程和N=16离子(Se编辑下面的评论)
integer, parameter :: ia(C) = [1,5,9,13]
integer, parameter :: ib(C) = [4,8,12,16]
然后库仑计算如下
!$omp parallel default(none) &
!$omp private(im, i,j,rji,r2inv) &
!$omp firstprivate(r_x,r_y,r_z, N, ia, ib) &
!$omp shared(a2_x, a2_y, a2_z)
im = omp_get_thread_num() + 1 ! How much threads
! Coulomb forces between each ion pair
! Compute the Coulomb force applied to ion i
do i = ia(im,1), ib(im,1) ! loop over threads
do j = 1, N ! loop over all ions
rji(1) = r_x(j) - r_x(i) ! distance between the ion i and j over x
rji(2) = r_y(j) - r_y(i) ! over y
rji(3) = r_z(j) - r_z(i) ! over z
! then compute the inverse square root of distance between the current ion i and the neighbor j
r2inv = 1.d0/dsqrt(rji(1)*rji(1) + rji(2)*rji(2) + rji(3)*rji(3) + softening)
r2inv = r2inv * r2inv * r2inv * alpha(1) ! alpha is 1/4.pi.eps0
! computation of the accelerations
a2_x(i) = a2_x(i) - rji(1)*r2inv
a2_y(i) = a2_y(i) - rji(2)*r2inv
a2_z(i) = a2_z(i) - rji(3)*r2inv
enddo
enddo
!$omp end parallel
问题
我正在尝试优化程序中这个耗时的部分。操作数量相当多,扩展速度很快,有N个。你能告诉我你对这个程序的看法吗?我有一些具体问题。
-
我被告知我应该将位置
r_x、r_y和r_z作为private变量,这对我来说似乎违反直觉,因为我想使用先前定义的离子位置进入该循环,所以我使用firstprivate。那正确吗 ? -
我不确定关于其他变量的并行化是否是最佳的。 rji 和 r2inv 不应该共享吗?因为为了计算离子 i 和 j 之间的距离,我“超越”了线程,你明白我的意思了吗?我需要分布在两个不同线程上的离子之间的信息。
-
我首先分裂离子的方式是否最佳?
-
我分别为每个离子遍历所有离子,这将在计算离子 i 和 i 之间的距离时导致除以零。为了防止这种情况,我定义了一个软化变量,它的值非常小,因此它不完全为零。我这样做是为了避免 if i==i 耗时。
-
平方根也可能很耗时?
如需任何其他详细信息,请随时询问。
编辑(备注)
-
我的电脑有一个 10 核 CPU Xeon W2155、32 Go RAM。我打算渲染大约 1000 个离子,同时考虑 4000 个,这需要很多时间。
-
我在其他可能消耗一些 CPU 时间的子例程中有这个 Coulomb 子例程。例如,一个可能很耗时的例程专门为每个离子生成随机数,具体取决于它们是否已经被激发,并应用正确的效果,无论它们是否吸收光子。所以这是很多 RNG,如果每个离子。
编辑(命题测试)
-
将
!$omp do与schedule(dynamic,1)、schedule(guided)或schedule(nonmonotonic:dynamic)和/或collapse(2)结合使用并没有缩短运行时间。它至少延长了三倍。建议我的模拟中的元素数量 (N) 太低,看不到明显的改进。如果我尝试渲染更多数量的元素(4096、8192 ...),我会尝试这些选项。 -
使用
!$omp do而不是在内核之间自制离子分布确实显示了运行时间方面的等效性。实施起来更容易,我会保留它。 -
用
**(-1/2)替换逆dsqrt表明在运行时间方面是等效的。 -
延迟平方根并将其与
r2inv的三次方相结合也是等效的。所以我把整个系列的操作换成了**(-1.5)。 -
与
rji(1)*r2inv的想法相同,我之前执行rji*r2inv并且仅在下一行中使用结果。
-
-
【问题讨论】:
-
只是对风格的评论 -
dsqrt非常 Fortran66。在过去的 50 年里,简单的sqrt就足够了。 -
一条评论:我假设 N=16 只是为了说明,因为对于如此少量的粒子,没有机会观察到多线程的显着加速。在实际应用中,您的典型 N 是多少?
-
@PierU 你的假设是对的。我宁愿使用 1024 个离子运行模拟,但我想尝试更多,例如 4096,但我希望对代码进行优化,因为使用 4096 个离子会耗时更多。在 1024 离子壁时间可以是 30 到 60 分钟,这很好,但在 4096 会更长。
标签: loops parallel-processing fortran openmp