大多数编译器是否优化 MATMUL(TRANSPOSE(A),B)?

Do most compilers optimize MATMUL(TRANSPOSE(A),B)?

在 Fortran 程序中,我需要计算几个表达式,例如 M · v , MT · v, MT · M, M · MT,等等... 这里,Mv 是二维和一维小数组(小于 100,通常大约2-10)

我想知道编写 MATMUL(TRANSPOSE(M),v) 是否会在编译时展开成与 MATMUL(N,v) 一样高效的代码,其中 N 显式存储为 N=TRANSPOSE(M)。我对具有 "strong" 优化标志(例如 -O2、-O3 或 -Ofast)的 g​​nu 和 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

为了使结果尽可能中立,我用完成一组等效操作的平均时间重新调整了答案。 我忽略了线程。

矩阵乘以向量

比较了六种不同的实现:

  1. 手动: do j=1,n; do k=1,n; w(j) = P(j,k)*v(k); end do; end do
  2. matmul: matmul(P,v)
  3. blas N:dgemv('N',n,n,1.0D0,P,n,v,1,0,w,1)
  4. matmul-t运行spose: matmul(transpose(P),v)
  5. matmul-t运行spose-tmp: Q=transpose(P); w=matmul(Q,v)
  6. blas T: dgemv('T',n,n,1.0D0,P,n,v,1,0,w,1)

在图 1 和图 2 中,您可以比较上述情况的时序结果。总的来说,我们可以说 gfortranifort 都不建议使用临时文件。两个编译器都可以更好地优化 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.

矩阵乘以矩阵

比较了六种不同的实现:

  1. 手动: 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
  2. matmul: matmul(P,P)
  3. blas N:dgemm('N','N',n,n,n,1.0D0,P,n,P,n,0.0D0,R,n)
  4. matmul-t运行spose: matmul(transpose(P),P)
  5. matmul-t运行spose-tmp: Q=transpose(P); matmul(Q,P)
  6. 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