我尝试通过 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/