- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
为了练习并行化 do 循环,我在 Fortran 中进行以下积分
$\integral{0}{1} \frac{4}{1+x^{2}} = \pi$
以下是我实现的代码:
program mpintegrate
integer i,nmax,nthreads,OMP_GET_NUM_THREADS
real xn,dx,value
real X(100000)
nthreads = 4
nmax = 100000
xn = 0.0
dx = 1.0/nmax
value = 0.0
do i=1,nmax
X(i) = xn
xn = xn + dx
enddo
call OMP_SET_NUM_THREADS(nthreads)
!$OMP Parallel
!$OMP Do Schedule(Static) Private(i,X)
do i=1,nmax
value = value + dx*(4.0/(1+X(i)*X(i)))
enddo
!$OMP End DO NoWait
!$OMP End Parallel
print *, value
end
我编译程序没有问题
gfortran -fopenmp -o mpintegrate mpintegrate.f
问题出在我执行程序时。当我按原样运行程序时,我得到的值范围为 (1,4)。但是,当我使用 omp do 循环取消注释 print 语句时,最终值大约是它应该的值 pi。
为什么value
中的答案不正确?
最佳答案
这里的一个问题是 X
不需要是私有(private)的(并且需要在平行线上指定,而不是在 do 线上);每个人都需要看到它,并且为每个线程拥有单独的副本是没有意义的。更糟糕的是,您从此处访问私有(private)副本获得的结果是未定义的,因为一旦您进入私有(private)区域,该私有(private)变量就尚未初始化。您可以使用 firstprivate
而不是 private
,后者使用并行区域之前的内容为您初始化它,但这里最简单/最好的只是 shared
.
让结束不等待
也没有多大意义,因为结束并行
无论如何都必须等待每个人都完成。
但是,话虽这么说,您仍然存在一个相当重大(且经典)的正确性问题。如果您在循环中更明确一点,那么这里发生的事情就会更清楚(为了清楚起见,放弃时间表,因为问题不依赖于所选的时间表):
!$OMP Parallel do Private(i) Default(none) Shared(value,X,dx,nmax)
do i=1,nmax
value = value + dx*(4.0/(1+X(i)*X(i)))
enddo
!$OMP End Parallel Do
print *, value
重复运行会给出不同的值:
$ ./foo
1.6643878
$ ./foo
1.5004054
$ ./foo
1.2746993
问题是所有线程都写入同一个共享变量值
。这是错误的 - 每个人都同时写入,结果是乱码,因为一个线程可以计算它自己的贡献,准备将其添加到 value
中,就在它即将这样做时,另一个线程可以这样做它写入值
,然后很快就会被破坏。并发写入同一个共享变量是一个经典race condition ,一个标准的错误系列,在 OpenMP 等共享内存编程中特别常见。
除了错误之外,它还很慢。由于内存系统中的争用,多个线程争夺相同的几个字节的内存(内存足够接近以落入同一高速缓存行)可能会非常慢。即使它们不是完全相同的变量(如本例所示),这种内存争用 - False Sharing如果它们恰好是相邻变量,则可以显着减慢速度。取出显式线程号设置,并使用环境变量:
$ export OMP_NUM_THREADS=1
$ time ./foo
3.1407621
real 0m0.003s
user 0m0.001s
sys 0m0.001s
$ export OMP_NUM_THREADS=2
$ time ./foo
3.1224852
real 0m0.007s
user 0m0.012s
sys 0m0.000s
$ export OMP_NUM_THREADS=8
$ time ./foo
1.1651508
real 0m0.008s
user 0m0.042s
sys 0m0.000s
因此,使用更多线程运行时,速度会慢近 3 倍(而且越来越错误)。
那么我们可以做些什么来解决这个问题呢?我们可以做的一件事是使用 atomic
指令确保每个人的添加不会互相覆盖:
!$OMP Parallel do Schedule(Static) Private(i) Default(none) Shared(X,dx, value, nmax)
do i=1,nmax
!$OMP atomic
value = value + dx*(4.0/(1+X(i)*X(i)))
enddo
!$OMP end parallel do
这解决了正确性问题:
$ export OMP_NUM_THREADS=8
$ ./foo
3.1407621
但对速度问题没有任何作用:
$ export OMP_NUM_THREADS=1
$ time ./foo
3.1407621
real 0m0.004s
user 0m0.001s
sys 0m0.002s
$ export OMP_NUM_THREADS=2
$ time ./foo
3.1407738
real 0m0.014s
user 0m0.023s
sys 0m0.001s
(请注意,使用不同数量的线程,您会得到略有不同的答案。这是由于最终总和的计算顺序与串行情况不同。对于单精度实数,由于不同的原因,差异会出现在第 7 位数字中。操作顺序很难避免,这里我们进行了 100,000 次操作。)
那么我们还能做什么呢?一种方法是让每个人记录自己的部分总和,然后在完成后将它们全部加起来:
!...
integer, parameter :: nthreads = 4
integer, parameter :: space=8
integer :: threadno
real, dimension(nthreads*space) :: partials
!...
partials=0
!...
!$OMP Parallel Private(value,i,threadno) Default(none) Shared(X,dx, partials)
value = 0
threadno = omp_get_thread_num()
!$OMP DO
do i=1,nmax
value = value + dx*(4.0/(1+X(i)*X(i)))
enddo
!$OMP END DO
partials((threadno+1)*space) = value
!$OMP end parallel
value = sum(partials)
print *, value
end
这是有效的 - 我们得到了正确的答案,如果你研究线程的数量,你会发现它非常快 - 我们已经在部分和数组中间隔开条目以避免错误共享(并且它是false,这一次,因为每个人都写入数组中的不同条目 - 不会覆盖)。
尽管如此,仅仅为了在线程之间获得正确的总和,这是一项愚蠢的工作量!有一种更简单的方法可以做到这一点 - OpenMP 有一个 reduction构造自动执行此操作(并且比上面的手工版本更有效:)
!$OMP Parallel do reduction(+:value) Private(i) Default(none) Shared(X,dx)
do i=1,nmax
value = value + dx*(4.0/(1+X(i)*X(i)))
enddo
!$OMP end parallel do
print *, value
现在程序可以正常运行,速度很快,而且代码也相当简单。最终的代码,在更现代的 Fortran 语言中,看起来像这样:
program mpintegrate
use omp_lib
integer, parameter :: nmax = 100000
real :: xn,dx,value
real :: X(nmax)
integer :: i
integer, parameter :: nthreads = 4
xn = 0.0
dx = 1.0/nmax
value = 0.0
partials=0
do i=1,nmax
X(i) = xn
xn = xn + dx
enddo
call omp_set_num_threads(nthreads)
!$OMP Parallel do reduction(+:value) Private(i) Default(none) Shared(X,dx)
do i=1,nmax
value = value + dx*(4.0/(1+X(i)*X(i)))
enddo
!$OMP end parallel do
print *, value
end
关于fortran - OpenMP 中对变量求和给出错误答案的线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24050218/
是的,我知道..,这不是想象的...这是一个真正的 Fortran 问题。 以前的版本是指 Fortran 2003、95、90,甚至 77。 我所说的“向后兼容”是指可以轻松运行为 2008 年以前
我有一个程序,它的变量中有一个值。一旦确定了该值,我想调用另一个程序并使用该变量的值来确定在新程序中的位置。有人知道该怎么做吗? 最佳答案 如果您有 Fortran 2008 编译器,您将拥有标准子例
namelist 是一种有用的 fortran 结构,可以从文件中快速初始化变量。 namelist 有一个名称并包含一组具有已知类型的变量。这使得它类似于 type 结构。 通常情况下,给程序或子例
我正在遍历索引,我正在检查我是否不在第一个循环交互和另一个条件中。如果第一个条件是 .False.,我不想评估第二个条件。 do i = 1, n if ( i /= 1 .and. var(
Fortran 2003 具有用于数组连接的方括号语法,Intel fortran 编译器也支持它。我在这里为矩阵连接写了一个简单的代码: program matrix implicit none r
我正在尝试通过重载类型名称来制作自定义数据类型构造函数。但是,在进行调用时,将调用默认构造函数。我不明白我做错了什么。 这是有问题的代码片段。 module test type, pu
我的最终目标是在 Fortran 中有一个通用的映射函数,即一个接受任意类型 A 的数组和一个 A->B 类型的函数的函数,将此函数应用于给定数组的所有元素并返回一个B 类型的数组。我无法用数组实现它
我正在学习 Fortran,在使用格式编写时发现了一些奇怪的东西(我使用的是 Fortran onlinegdb) Program Hello real, dimension(3,2):: array
Fortran 中的INTERFACE 语句是否使其成为正式实现multiple dispatch 的编程语言? ? (我问是因为所链接的维基百科文章在其看似全面的支持相关范式的示例编程语言列表中并未
我可以使用 Fortran 95 编译器编译 Fortran 90 文件吗? Fortran 95 似乎有很多,但 Fortran 90 没有。 最佳答案 这个可以: NAGWare f95 Comp
嗨,我在 Fortran 中对二维离散化问题强加边界条件时遇到了麻烦。我的离散化网格是一个二维正方形,在 x,y 方向上从 -L 到 L。 我想强加这样的边界条件, 在 x=L 的边界线上,指定了函数
Fortran 是否有与 C assert 等效的标准函数/关键字? ? 我找不到 assert我在Fortran2003标准中提到过。我发现了一些如何使用预处理器的方法,但是在这个 answer建议
我有一系列的作业,使用“;”将它们分配给同一个ike。分开statemnts,但我收到此错误: 1.0;磅(1,9) 1个 错误:(1)处无法分类的陈述 在文件LJ.F90:223中 如果每个语句都在
我正在使用 gfortran -std=f2008。我有一个函数,它返回一个包含可分配数组的派生类型。该函数在返回之前调用allocate()。似乎在分配数组的函数返回之后,数组会自动释放一段时间,并
我制作了这个小型测试程序来“证明”在编译之前(或者如果你让它们可分配),你不能在不指定它们的大小的情况下使用向量。我的观点失败了。我期待本地向量“num”会失败。程序在执行程序之前无法知道它的大小。大
出于优化原因,Fortran 强制子例程或函数的虚拟参数不是别名,即它们不指向相同的内存位置。 我想知道相同的约束是否适用于函数的返回值。 换句话说,对于给定的 myfunc 函数: function
我已经在Fortran 90中编写了一个相当大的程序。它已经运行了一段时间了,但是今天我尝试将其提高一个档次并增加问题的大小(这是研究非标准的有限元求解器,如果那样的话)。可以帮助任何人...)现在,
在 C 和 C++ 中,有许多操作会导致未定义的行为,即允许编译器做任何它想做的事情的情况。 Examples包括在释放变量后使用它,释放变量两次和取消引用空指针。 Fortran 是否也有未定义的行
通常我使用fortran进行数值分析,然后使用matlab、R和python进行后期和前期工作。 我发现 matlab、R 和 python 在终端中提供了命令提示符,以便您可以运行脚本以及从命令行立
在 Fortran 中将变量设置为 +Infinity 的最安全方法是什么?目前我正在使用: program test implicit none print *,infinity() con
我是一名优秀的程序员,十分优秀!