我正在使用Fortran
和gfortran
编译我的-mavx
代码并验证某些指令是通过objdump
进行矢量化的,但我没有得到速度提升我期待,所以我想确保以下参数被矢量化(这条单指令约为运行时的50%)。
我知道某些指令可以被矢量化,而其他指令则不能,所以我想确保这可以是:
sum(A(i1:i2,ir))
同样,由于我在一个非常大的矩阵上执行此操作,因此这一行占用了大约50%的运行时间。我可以提供更多关于为什么我这样做的信息,但足以说它是必要的,尽管我可以在必要时重构内存(例如,我可以将总和作为{{1如果可以改为矢量化。
这条线是否被矢量化?我该怎么说?如果没有矢量化,我该如何强制进行矢量化?
编辑:感谢评论,我现在意识到我可以通过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
时,和 ONLY 会进行矢量化,但我确实看到由于矢量化而增加了70%的速度。问题仍然存在:为什么-funsafe-math-optimizations
没有矢量化,如何才能得到一个简单的sum(A(i1:i2,ir))
来进行矢量化?
答案 0 :(得分:1)
事实证明,除非我包含-ffast-math
或-funsafe-math-optimizations
,否则我无法使用矢量化。
我玩的两段代码片段是:
tsum = 0.0d0
tvec(1:n) = A(i1:i2, ir)
do ii = 1,n
tsum = tsum + tvec(ii)
enddo
和
tsum = sum(A(i1:i2,ir))
以下是运行带有不同编译选项的第一个代码段时的时间:
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-math-optimizations
进行比较时,它会显示约70%的加速比。 (请注意,这些只是每次运行一次 - 在我们发布之前,我们将在多次运行中进行真正的基准测试。)
我确实受到了轻微的打击。当我使用-f
选项时,我的值略有变化。没有它们,我的变量(v1
,v2
)的错误是:
v1 ... 5.60663e-15 9.71445e-17 1.05471e-15
v2 ... 5.11674e-14 1.79301e-14 2.58127e-15
但是通过优化,错误是:
v1 ... 7.11931e-15 5.39846e-15 3.33067e-16
v2 ... 1.97273e-13 6.98608e-14 2.17742e-14
表示确实存在不同的事情。
答案 1 :(得分:0)
您的显式循环版本仍然会以与矢量化版本不同的顺序添加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添加单元饱和。 Skylake每个时钟可以增加两个FP,延迟= 4。以前的英特尔CPU每个时钟执行一次,延迟= 3。所以在Skylake上,你需要8个向量累加器才能使FP单元饱和。当然它们必须是256b向量,因为AVX指令速度快但是SSE向量指令的工作量是其两倍。
使用8 * 8累加器变量编写源代码将是荒谬的,所以我猜你需要-ffast-math
,或者一个OpenMP pragma,告诉编译器不同的操作顺序是正常的。
显式展开您的源意味着您必须处理不是向量宽度*展开的倍数的循环计数。如果你对事物施加限制,它可以帮助编译器避免生成多个版本的循环或额外的循环设置/清理代码。