c++ - 为什么 Armadillo 矩阵计算比 Fortran 慢得多

标签 c++ fortran armadillo

我尝试通过 Armadillo 库使用矩阵实现将代码从 Fortran 重写为 C++。两种代码的结果相同,但 C++ 代码比 Fortran (> 10x) 慢得多。这些代码涉及小矩阵(2x2、4x4)的逆、乘法和加法。我在这里放了一部分类似的代码进行测试。

============================

clang++ cplusplus.cc -o cplusplus --std=c++14 -larmadillo -O2

ifort fort.f90 -o fort -O2

C++ 代码时间:0.39404s

Fortran 代码时间:0.068s

============================

C++代码:

#include <armadillo>
#include <iostream>

int main()
{
  const int niter = 1580000;
  const int ns = 3;
  arma::cx_cube m1(2, 2, ns), m2(2, 2, ns), m3(2, 2, ns);
  arma::wall_clock timer;
  timer.tic();
  for (auto i=0; i<niter; ++i) {
    for (auto j=0; j<ns; ++j)
      m1.slice(j) += m2.slice(j) * m3.slice(j);
  }
  double n = timer.toc();
  std::cout << "time: " << n << "s" << std::endl;
  return 0;
}

Fortran 代码:

program main
  implicit none
  integer, parameter :: ns = 3, niter = 1580000
  complex*16 m1(2, 2, ns), m2(2, 2, ns), m3(2, 2, ns)
  integer i, j
  real :: start, finish
  call cpu_time(start)
  do i = 1, niter
     do j = 1, ns
        m1(1, 1, j) = m1(1, 1, j) + m2(1, 1, j) * m3(1, 1, j) + m2(1, 2, j) * m3(2, 1, j)
        m1(1, 2, j) = m1(1, 2, j) + m2(1, 1, j) * m3(1, 2, j) + m2(1, 2, j) * m3(2, 2, j)
        m1(2, 1, j) = m1(2, 1, j) + m2(2, 1, j) * m3(1, 1, j) + m2(2, 2, j) * m3(2, 1, j)
        m1(2, 2, j) = m1(2, 2, j) + m2(2, 1, j) * m3(1, 2, j) + m2(2, 2, j) * m3(2, 2, j)
     end do
  end do
  call cpu_time(finish)
  print *, "time: ", finish-start, " s"

end program main

============================================= =====================

遵循@ewcz @user5713492 的建议

============================

clang++ cplusplus.cc -o cplusplus --std=c++14 -larmadillo -O2

ifort fort.f90 -o fort -O2

ifort fort2.f90 -o fort2 -O2

C++代码(cplusplus.cc)时间:0.39650s

Fortran代码(fort.f90)(显式运行)时间:0.020s

Fortran 代码(fort2.f90) (matmul) 时间:0.064s

============================

C++代码(cplusplus.cc):

#include <armadillo>
#include <iostream>
#include <complex>

int main()
{
  const int niter = 1580000;
  const int ns = 3;
  arma::cx_cube m1(2, 2, ns, arma::fill::ones),
    m2(2, 2, ns, arma::fill::ones),
    m3(2, 2, ns,arma::fill::ones);
  std::complex<double> result;
  arma::wall_clock timer;
  timer.tic();
  for (auto i=0; i<niter; ++i) {
    for (auto j=0; j<ns; ++j)
      m1.slice(j) += m2.slice(j) * m3.slice(j);
  }

  double n = timer.toc();
  std::cout << "time: " << n << "s" << std::endl;
  result = arma::accu(m1);
  std::cout << result << std::endl;
  return 0;
}

Fortran 代码(fort.f90):

program main
  implicit none
  integer, parameter :: ns = 3, niter = 1580000
  complex*16 m1(2, 2, ns), m2(2, 2, ns), m3(2, 2, ns)
  integer i, j
  complex*16 result
  real :: start, finish
  m1 = 1
  m2 = 1
  m3 = 1
  call cpu_time(start)
  do i = 1, niter
     do j = 1, ns
        m1(1, 1, j) = m1(1, 1, j) + m2(1, 1, j) * m3(1, 1, j) + m2(1, 2, j) * m3(2, 1, j)
        m1(1, 2, j) = m1(1, 2, j) + m2(1, 1, j) * m3(1, 2, j) + m2(1, 2, j) * m3(2, 2, j)
        m1(2, 1, j) = m1(2, 1, j) + m2(2, 1, j) * m3(1, 1, j) + m2(2, 2, j) * m3(2, 1, j)
        m1(2, 2, j) = m1(2, 2, j) + m2(2, 1, j) * m3(1, 2, j) + m2(2, 2, j) * m3(2, 2, j)
     end do
  end do
  call cpu_time(finish)
  result = sum(m1)
  print *, "time: ", finish-start, " s"
  print *, result

end program main

Fortran 代码(fort2.f90):

program main
  implicit none
  integer, parameter :: ns = 3, niter = 1580000
  complex*16 m1(2, 2, ns), m2(2, 2, ns), m3(2, 2, ns)
  integer i, j
  complex*16 result
  real :: start, finish
  m1 = 1
  m2 = 1
  m3 = 1
  call cpu_time(start)
  do i = 1, niter
     do j = 1, ns
        m1(:,:,j) = m1(:,:,j)+matmul(m2(:,:,j),m3(:,:,j))
     end do
  end do
  call cpu_time(finish)
  result = sum(m1)
  print *, "time: ", finish-start, " s"
  print *, result

end program main

============================================= =======================

复数可能是 Armadillo 运行缓慢的原因之一。如果我在 C++ 中使用 arma::cube 而不是 arma::cx_cube 并在 Fortran 中使用 real*8,时间是:

C++代码时间:0.08s

Fortran代码(fort.f90)(显式运行)时间:0.012s

Fortran 代码(fort2.f90) (matmul) 时间:0.028s

但是,我的计算需要复数。奇怪的是 armadillo 库的计算时间增加非常大,但 Fortran 的计算时间增加了一点。

最佳答案

您没有在 gfortran 中计时。它可以在级别 -O2 看到您没有使用 m1 的值,因此它完全跳过了计算。同样在 Fortran 中,您的数组未初始化,因此您可以使用 NaN 进行计算,这可能会大大降低速度。

因此您应该初始化数组并使用某种输入,例如命令行、用户输入或文件内容,这样编译器就无法预先计算结果。

那么你可能会考虑将 Fortran 中的循环内容更改为

m1(:,:,j) = m1(:,:,j)+matmul(m2(:,:,j),m3(:,:,j))

为了和C++的东西保持一致。 (这样做时 gfortran 似乎慢了很多,但 ifort 对此非常满意。)

然后您必须在末尾打印出您的数组,这样编译器就不会断定您计时的循环可以像 gfortran 那样跳过。编辑修复并让我们知道新结果。

关于c++ - 为什么 Armadillo 矩阵计算比 Fortran 慢得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47919714/

相关文章:

c++ - std::string 是否有空终止符?

fortran - 当 Fortran 生成大型内部临时数组时,如何避免堆栈溢出?

c++ - Armadillo 中的字段初始化

r - Armadillo 从矢量 reshape 为立方体

c++ - 检查可变参数模板参数的唯一性

c++ - 选择和移动事件在 QGraphicsScene 中不起作用

c++ - 为什么 Boost 参数选择继承而不是组合?

eclipse - 有没有适用于 intel fortran 编译器的 eclipse 插件?

fortran - Fortran 中的字符串 : Portable LEN_TRIM and LNBLNK?

c++ - 将对象参数作为 const 传递并读取它