为什么矩阵加法比特征中的矩阵向​​量乘法慢?

com*_*ter 2 c++ performance matrix eigen

为什么矩阵加法比矩阵向量乘法需要更长的时间?

矩阵仅添加成本n ^ 2 add,而Matrix-Vector Multiplication需要n*(n-1)add和n ^ 2乘法.

但是,在Eigen中,Matrix Add需要的时间是Matrix-Vector Multiplication的两倍.是否存在加速Eigen中Matrix Add操作的选项?

#include <eigen3/Eigen/Eigen>
#include <iostream>
#include <ctime>
#include <string>
#include <chrono>
#include <fstream>
#include <random>
#include <iomanip>

using namespace Eigen;
using namespace std;

int main()
{
const int l=100;
MatrixXf m=MatrixXf::Random(l,l);
MatrixXf n=MatrixXf::Random(l,l);
VectorXf v=VectorXf::Random(l,1);

MatrixXf qq=MatrixXf::Random(l,1);
MatrixXf pp=MatrixXf::Random(l,l);

auto start = chrono::steady_clock::now();
for(int j=0;j<10000;j++)
qq=m*v;
auto end = chrono::steady_clock::now();
double time_duration=chrono::duration_cast<chrono::milliseconds>(end - start).count();
std::cout << setprecision(6) << "Elapsed time in seconds : "<< time_duration/1000<< "s" << std::endl;
auto start1 = chrono::steady_clock::now();
for(int j=0;j<10000;j++)
pp=m+n;
auto end1 = chrono::steady_clock::now();
double time_duration1=chrono::duration_cast<chrono::milliseconds>(end1 - start1).count();
std::cout << setprecision(6) << "Elapsed time in seconds : "<< time_duration1/1000<< "s" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

测试1:没有任何优化:

编译命令:g ++ - 8 -test.cpp -o test

运行命令:./ test

经过的时间以秒为单位:0.323秒

经过的时间以秒为单位:0.635秒

测试2:使用-march =原生优化:

g ++ - 8 test.cpp -march = native -o test

运行命令:./ test

以秒为单位的经过时间:0.21秒

经过的时间以秒为单位:0.372秒

测试3:使用-O3优化:

编译命令:g ++ - 8 -test.cpp -O3 -o test

运行命令:./ test

经过的时间以秒为单位:0.009s

以秒为单位的经过时间:0.016秒

测试4:使用-march = native,-O3优化:

编译命令:g ++ - 8 -test.cpp -march = native -O3 -o test

运行命令:./ test

经过的时间以秒为单位:0.008秒

以秒为单位的经过时间:0.016秒

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

我注意到编译器可能作弊的注释,因为我没有使用上一次迭代的结果.为了解决这个问题,我改为进行一次迭代,并使用更大的尺寸进行稳定的时间统计.

#include <eigen3/Eigen/Eigen>
#include <iostream>
#include <ctime>
#include <string>
#include <chrono>
#include <fstream>
#include <random>
#include <iomanip>

using namespace Eigen;
using namespace std;

int main()
{
const int l=1000;
MatrixXf m=MatrixXf::Random(l,l);
MatrixXf n=MatrixXf::Random(l,l);
VectorXf v=VectorXf::Random(l,1);

MatrixXf qq=MatrixXf::Random(l,1);
MatrixXf pp=MatrixXf::Random(l,l);

auto start = chrono::steady_clock::now();
qq=m*v;
auto end = chrono::steady_clock::now();
double time_duration=chrono::duration_cast<chrono::microseconds>(end - start).count();

auto start1 = chrono::steady_clock::now();
pp=m+n;
auto end1 = chrono::steady_clock::now();
double time_duration1=chrono::duration_cast<chrono::microseconds>(end1 - start1).count();
std::cout << setprecision(6) << "Elapsed time in microseconds : "<< time_duration<< "us" << std::endl;
std::cout << setprecision(6) << "Elapsed time in microseconds : "<< time_duration1<< "us" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

测试1:没有任何优化:

编译命令:g ++ - 8 -test.cpp -o test

运行命令:./ test

经过的时间,以微秒为单位:3125us

经过的时间以微秒为单位:6849us

测试2:使用-march =原生优化:

g ++ - 8 test.cpp -march = native -o test

运行命令:./ test

经过的时间以微秒为单位:1776us

经过的时间以微秒为单位:3815us

测试3:使用-O3优化:

编译命令:g ++ - 8 -test.cpp -O3 -o test

运行命令:./ test

经过的时间以微秒为单位:449us

经过的时间以微秒为单位:760us

测试4:使用-march = native,-O3优化:

编译命令:g ++ - 8 -test.cpp -march = native -O3 -o test

运行命令:./ test

经过的时间以微秒为单位:351us

经过的时间以微秒为单位:871us

gga*_*ael 5

简短回答:您计算了操作次数,但忽略了计算内存访问次数,对于附加案例,这些内存访问的负载将近x2.详情如下.

首先,两种操作的实际操作次数是相同的,因为现代CPU能够同时执行一次独立的加法和乘法.两个连续的mul/add like x*y+z甚至可以融合为具有与1次加法或1次乘法相同的成本的单个操作.如果您的CPU支持FMA,那么就会发生这种情况-march=native,但我怀疑FMA在这里扮演什么角色.

其次,在你的计算中,你忘了测量内存访问的次数.回想一下,除非数据已经在L1缓存中,否则一个内存负载比一个或多个mul贵得多.

对于添加它很容易:我们有2*n^2很多缓存未命中的加载,以及n^2商店.

对于具有列主矩阵的矩阵向量乘积,输入向量只读一次,因此n^2+n加载输入,并且由于列一次由4列的块处理,我们n^2/4对输出向量进行读写但是几乎没有缓存未命中,因为它适合L1缓存.因此总体而言,添加的内存负载比矩阵矢量产品高出近x2,因此x2速度因子并非异常.

此外,矩阵矢量代码通过显式循环剥离进行了更积极的优化,但我怀疑这会在此基准测试中产生任何差异,因为您的矩阵根本不适合L1缓存.