大多数编译器是否优化 MATMUL(TRANSPOSE(A),B)?
Do most compilers optimize MATMUL(TRANSPOSE(A),B)?
在 Fortran 程序中,我需要计算几个表达式,例如 M · v , MT · v, MT · M, M · MT,等等...
这里,M 和 v 是二维和一维小数组(小于 100,通常大约2-10)
我想知道编写 MATMUL(TRANSPOSE(M),v)
是否会在编译时展开成与 MATMUL(N,v)
一样高效的代码,其中 N
显式存储为 N=TRANSPOSE(M)
。我对具有 "strong" 优化标志(例如 -O2、-O3 或 -Ofast)的 gnu 和 ifort 编译器特别感兴趣。
下面是各种方法的几个执行时间。
system:
- Intel(R) Core(TM) i5-6500T CPU @ 2.50GHz
- cache size : 6144 KB
- RAM : 16MB
- GNU Fortran (GCC) 6.3.1 20170216 (Red Hat 6.3.1-3)
- ifort (IFORT) 18.0.5 20180823
- BLAS : for gnu compiler, the used blas is the default version
compilation:
[gnu] $ gfortran -O3 x.f90 -lblas
[intel] $ ifort -O3 -mkl x.f90
execution:
[gnu] $ ./a.out > matmul.gnu.txt
[intel] $ EXPORT MKL_NUM_THREADS=1; ./a.out > matmul.intel.txt
为了使结果尽可能中立,我用完成一组等效操作的平均时间重新调整了答案。
我忽略了线程。
矩阵乘以向量
比较了六种不同的实现:
- 手动:
do j=1,n; do k=1,n; w(j) = P(j,k)*v(k); end do; end do
- matmul:
matmul(P,v)
- blas N:
dgemv('N',n,n,1.0D0,P,n,v,1,0,w,1)
- matmul-t运行spose:
matmul(transpose(P),v)
- matmul-t运行spose-tmp:
Q=transpose(P); w=matmul(Q,v)
- blas T:
dgemv('T',n,n,1.0D0,P,n,v,1,0,w,1)
在图 1 和图 2 中,您可以比较上述情况的时序结果。总的来说,我们可以说 gfortran
和 ifort
都不建议使用临时文件。两个编译器都可以更好地优化 MATMUL(TRANSPOSE(P),v)
。在 gfortran
中,MATMUL
的实现比默认的 BLAS 更快,ifort
清楚地表明 mkl-blas
更快。
图 1: 矩阵向量乘法。 gfortran
上各种实现的比较 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n2 × δ。这里 δ 是 size 1000 人工计算的平均时间除以 1000 × 1000.
图 2: 矩阵向量乘法。在单线程 ifort
编译上比较各种实现 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n2 × δ。这里 δ 是 size 1000 人工计算的平均时间除以 1000 × 1000.
矩阵乘以矩阵
比较了六种不同的实现:
- 手动:
do l=1,n; do j=1,n; do k=1,n; Q(j,l) = P(j,k)*P(k,l); end do; end do; end do
- matmul:
matmul(P,P)
- blas N:
dgemm('N','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
- matmul-t运行spose:
matmul(transpose(P),P)
- matmul-t运行spose-tmp:
Q=transpose(P); matmul(Q,P)
- blas T:
dgemm('T','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
在图 3 和图 4 中,您可以比较上述情况的时序结果。与 vector-case 相反,临时的使用只建议用于 gfort运行。虽然在 gfortran
中,MATMUL
的实现比默认的 BLAS 更快,但 ifort
清楚地表明 mkl-blas
更快。值得注意的是,手动实现与 mkl-blas
.
相当
图3:矩阵-矩阵乘法。 gfortran
上各种实现的比较 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n3 × δ。这里的δ是人工计算尺寸1000除以1000×1000×1000的平均时间
图4:矩阵-矩阵乘法。在单线程 ifort
编译上比较各种实现 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n3 × δ。这里的δ是人工计算尺寸1000除以1000×1000×1000的平均时间
使用代码:
program matmul_test
implicit none
double precision, dimension(:,:), allocatable :: P,Q,R
double precision, dimension(:), allocatable :: v,w
integer :: n,i,j,k,l
double precision,dimension(12) :: t1,t2
do n = 1,1000
allocate(P(n,n),Q(n,n), R(n,n), v(n),w(n))
call random_number(P)
call random_number(v)
i=0
i=i+1
call cpu_time(t1(i))
do j=1,n; do k=1,n; w(j) = P(j,k)*v(k); end do; end do
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
w=matmul(P,v)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemv('N',n,n,1.0D0,P,n,v,1,0,w,1)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
w=matmul(transpose(P),v)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=transpose(P)
w=matmul(Q,v)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemv('T',n,n,1.0D0,P,n,v,1,0,w,1)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
do l=1,n; do j=1,n; do k=1,n; Q(j,l) = P(j,k)*P(k,l); end do; end do; end do
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=matmul(P,P)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemm('N','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=matmul(transpose(P),P)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=transpose(P)
R=matmul(Q,P)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemm('T','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
call cpu_time(t2(i))
write(*,'(I6,12D25.17)') n, t2-t1
deallocate(P,Q,R,v,w)
end do
end program matmul_test
在 Fortran 程序中,我需要计算几个表达式,例如 M · v , MT · v, MT · M, M · MT,等等... 这里,M 和 v 是二维和一维小数组(小于 100,通常大约2-10)
我想知道编写 MATMUL(TRANSPOSE(M),v)
是否会在编译时展开成与 MATMUL(N,v)
一样高效的代码,其中 N
显式存储为 N=TRANSPOSE(M)
。我对具有 "strong" 优化标志(例如 -O2、-O3 或 -Ofast)的 gnu 和 ifort 编译器特别感兴趣。
下面是各种方法的几个执行时间。
system:
- Intel(R) Core(TM) i5-6500T CPU @ 2.50GHz
- cache size : 6144 KB
- RAM : 16MB
- GNU Fortran (GCC) 6.3.1 20170216 (Red Hat 6.3.1-3)
- ifort (IFORT) 18.0.5 20180823
- BLAS : for gnu compiler, the used blas is the default version
compilation:
[gnu] $ gfortran -O3 x.f90 -lblas [intel] $ ifort -O3 -mkl x.f90
execution:
[gnu] $ ./a.out > matmul.gnu.txt [intel] $ EXPORT MKL_NUM_THREADS=1; ./a.out > matmul.intel.txt
为了使结果尽可能中立,我用完成一组等效操作的平均时间重新调整了答案。 我忽略了线程。
矩阵乘以向量
比较了六种不同的实现:
- 手动:
do j=1,n; do k=1,n; w(j) = P(j,k)*v(k); end do; end do
- matmul:
matmul(P,v)
- blas N:
dgemv('N',n,n,1.0D0,P,n,v,1,0,w,1)
- matmul-t运行spose:
matmul(transpose(P),v)
- matmul-t运行spose-tmp:
Q=transpose(P); w=matmul(Q,v)
- blas T:
dgemv('T',n,n,1.0D0,P,n,v,1,0,w,1)
在图 1 和图 2 中,您可以比较上述情况的时序结果。总的来说,我们可以说 gfortran
和 ifort
都不建议使用临时文件。两个编译器都可以更好地优化 MATMUL(TRANSPOSE(P),v)
。在 gfortran
中,MATMUL
的实现比默认的 BLAS 更快,ifort
清楚地表明 mkl-blas
更快。
gfortran
上各种实现的比较 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n2 × δ。这里 δ 是 size 1000 人工计算的平均时间除以 1000 × 1000.
ifort
编译上比较各种实现 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n2 × δ。这里 δ 是 size 1000 人工计算的平均时间除以 1000 × 1000.
矩阵乘以矩阵
比较了六种不同的实现:
- 手动:
do l=1,n; do j=1,n; do k=1,n; Q(j,l) = P(j,k)*P(k,l); end do; end do; end do
- matmul:
matmul(P,P)
- blas N:
dgemm('N','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
- matmul-t运行spose:
matmul(transpose(P),P)
- matmul-t运行spose-tmp:
Q=transpose(P); matmul(Q,P)
- blas T:
dgemm('T','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
在图 3 和图 4 中,您可以比较上述情况的时序结果。与 vector-case 相反,临时的使用只建议用于 gfort运行。虽然在 gfortran
中,MATMUL
的实现比默认的 BLAS 更快,但 ifort
清楚地表明 mkl-blas
更快。值得注意的是,手动实现与 mkl-blas
.
gfortran
上各种实现的比较 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n3 × δ。这里的δ是人工计算尺寸1000除以1000×1000×1000的平均时间
ifort
编译上比较各种实现 运行。左侧面板显示绝对时间除以大小为 1000 的系统的手动计算总时间。右侧面板显示绝对时间除以 n3 × δ。这里的δ是人工计算尺寸1000除以1000×1000×1000的平均时间
使用代码:
program matmul_test
implicit none
double precision, dimension(:,:), allocatable :: P,Q,R
double precision, dimension(:), allocatable :: v,w
integer :: n,i,j,k,l
double precision,dimension(12) :: t1,t2
do n = 1,1000
allocate(P(n,n),Q(n,n), R(n,n), v(n),w(n))
call random_number(P)
call random_number(v)
i=0
i=i+1
call cpu_time(t1(i))
do j=1,n; do k=1,n; w(j) = P(j,k)*v(k); end do; end do
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
w=matmul(P,v)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemv('N',n,n,1.0D0,P,n,v,1,0,w,1)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
w=matmul(transpose(P),v)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=transpose(P)
w=matmul(Q,v)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemv('T',n,n,1.0D0,P,n,v,1,0,w,1)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
do l=1,n; do j=1,n; do k=1,n; Q(j,l) = P(j,k)*P(k,l); end do; end do; end do
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=matmul(P,P)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemm('N','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=matmul(transpose(P),P)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
Q=transpose(P)
R=matmul(Q,P)
call cpu_time(t2(i))
i=i+1
call cpu_time(t1(i))
call dgemm('T','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
call cpu_time(t2(i))
write(*,'(I6,12D25.17)') n, t2-t1
deallocate(P,Q,R,v,w)
end do
end program matmul_test