Fortran中的矢量化和
我正在使用Fortran中的矢量化和,fortran,sse,gfortran,simd,avx,Fortran,Sse,Gfortran,Simd,Avx,我正在使用gfortran和-mavx编译我的Fortran代码,并且已经验证了一些指令是通过objdump矢量化的,但是我没有得到我期望的速度改进,所以我想确保以下参数是矢量化的(这条指令约占运行时的50%) 我知道有些指令可以矢量化,而有些指令不能矢量化,因此我想确保: sum(A(i1:i2,ir)) 同样,这一行占用了大约50%的运行时间,因为我是在一个非常大的矩阵上进行这项工作的。我可以提供更多关于我为什么这样做的信息,但足以说明这是必要的,尽管我可以在必要时重新构造内存(例如,我可以
gfortran
和-mavx
编译我的Fortran
代码,并且已经验证了一些指令是通过objdump
矢量化的,但是我没有得到我期望的速度改进,所以我想确保以下参数是矢量化的(这条指令约占运行时的50%)
我知道有些指令可以矢量化,而有些指令不能矢量化,因此我想确保:
sum(A(i1:i2,ir))
同样,这一行占用了大约50%的运行时间,因为我是在一个非常大的矩阵上进行这项工作的。我可以提供更多关于我为什么这样做的信息,但足以说明这是必要的,尽管我可以在必要时重新构造内存(例如,我可以按sum(a(ir,i1:i2))进行求和
如果可以改为矢量化
这条线是矢量化的吗?我怎么知道?如果它不是矢量化的,我怎么强制矢量化
编辑:多亏了这些评论,我现在意识到我可以通过-ftree vectorizer verbose
检查此总和的矢量化,并发现这不是矢量化。我已按如下方式重新构造代码:
tsum = 0.0d0
tn = i2 - i1 + 1
tvec(1:tn) = A(i1:i2, ir)
do ii = 1,tn
tsum = tsum + tvec(ii)
enddo
当我打开
-funsafe math optimizations
时,这只矢量化,但我确实看到矢量化又提高了70%的速度。问题仍然存在:为什么求和(A(i1:i2,ir))
不矢量化,如何获得简单的求和
进行矢量化?事实证明,除非我包含-ffast math
或-funsafe math优化,否则我无法使用矢量化
我使用的两个代码片段是:
tsum = 0.0d0
tvec(1:n) = A(i1:i2, ir)
do ii = 1,n
tsum = tsum + tvec(ii)
enddo
及
下面是我使用不同编译选项运行第一个代码段的次数:
10.62 sec ... None
10.35 sec ... -mtune=native -mavx
7.44 sec ... -mtune-native -mavx -ffast-math
7.49 sec ... -mtune-native -mavx -funsafe-math-optimizations
最后,通过这些相同的优化,我能够向量化tsum=sum(A(i1:i2,ir))
以获得
7.96 sec ... None
8.41 sec ... -mtune=native -mavx
5.06 sec ... -mtune=native -mavx -ffast-math
4.97 sec ... -mtune=native -mavx -funsafe-math-optimizations
当我们将sum
和-mtune=native-mavx
与-mtune=native-mavx-funsafe数学优化
进行比较时,它显示了约70%的加速(注意,这些优化每次只运行一次-在发布之前,我们将对多次运行进行真正的基准测试)
不过,我确实受到了一些影响。当我使用-f
选项时,我的值会略有变化。如果没有这些选项,我的变量(v1
,v2
)的错误如下:
但通过优化,错误是:
v1 ... 7.11931e-15 5.39846e-15 3.33067e-16
v2 ... 1.97273e-13 6.98608e-14 2.17742e-14
这表明确实发生了一些不同的事情。您的显式循环版本仍然以不同于矢量化版本的顺序执行FP添加。矢量版本使用4个累加器,每个累加器获得第4个数组元素
您可以编写源代码以匹配向量版本的功能:
tsum0 = 0.0d0
tsum1 = 0.0d0
tsum2 = 0.0d0
tsum3 = 0.0d0
tn = i2 - i1 + 1
tvec(1:tn) = A(i1:i2, ir)
do ii = 1,tn,4 ! count by 4
tsum0 = tsum0 + tvec(ii)
tsum1 = tsum1 + tvec(ii+1)
tsum2 = tsum2 + tvec(ii+2)
tsum3 = tsum3 + tvec(ii+3)
enddo
tsum = (tsum0 + tsum1) + (tsum2 + tsum3)
这可能在没有-ffast math
的情况下进行矢量化
FP add具有多周期延迟,但每个时钟吞吐量只有一个或两个,因此需要asm使用多个向量累加器使FP add单元饱和.Skylake可以对每个时钟进行两次FP加法,延迟为4。以前的Intel CPU对每个时钟进行一次FP加法,延迟为3。因此在Skylake上,需要8个向量累加器对FP单元进行饱和。当然,它们必须是256b向量,因为AVX指令的速度与SSE向量指令一样快,但所做的工作是SSE向量指令的两倍
用8*8累加器变量编写源代码是荒谬的,因此我想您需要-ffast math
,或者一个OpenMP pragma,告诉编译器不同的操作顺序是可以的
显式展开源代码意味着您必须处理不是向量宽度*展开倍数的循环计数。如果您对某些内容进行限制,它可以帮助编译器避免生成多个版本的循环或额外的循环设置/清理代码。不太重复,但:Fortran以列主方式访问数组,即您r当前版本比sum(A(ir,i1:i2))快得多。
。当前版本访问一个连续的内存块。关于您的编辑:非常有趣。我本来希望总和是矢量化的。您是否使用sum vs loop进行过实际的基准测试?是否使用过优化开关?更新:?OpenMP 4“simd”子句和缩减?您还可以尝试使用-fopt info
来获取更多信息(这在man-gcc
中有说明)。我想,有某种形式的循环展开。嗯,我想优化的不安全性的一个来源可能来自重新安排操作顺序。我不认为机器精度方面的错误是致命的。如果tn不是4的倍数,那么循环会遗漏一些还是走得太远?@Laurbert515:是的,一个或另一个。如果我知道Fortran,我可以告诉你是哪一个。:P这是我最后一段描述的问题:展开导致需要清理代码来处理奇数次迭代。让编译器通过使用-ffast math
或OpenMP来解决这一问题将节省大量调试(或者至少花点时间思考一下,确保一开始不会引入bug。)
v1 ... 7.11931e-15 5.39846e-15 3.33067e-16
v2 ... 1.97273e-13 6.98608e-14 2.17742e-14
tsum0 = 0.0d0
tsum1 = 0.0d0
tsum2 = 0.0d0
tsum3 = 0.0d0
tn = i2 - i1 + 1
tvec(1:tn) = A(i1:i2, ir)
do ii = 1,tn,4 ! count by 4
tsum0 = tsum0 + tvec(ii)
tsum1 = tsum1 + tvec(ii+1)
tsum2 = tsum2 + tvec(ii+2)
tsum3 = tsum3 + tvec(ii+3)
enddo
tsum = (tsum0 + tsum1) + (tsum2 + tsum3)