稀疏矩阵向量乘法

文章介绍了稀疏矩阵向量乘法(SpMV)的概念及其在科学计算中的应用,重点讲解了CompressedSparseRow(CSR)存储格式,并提供了基本的SpMV实现。此外,还讨论了使用OpenMP进行并行优化的方法,以提高计算效率。
摘要由CSDN通过智能技术生成

稀疏矩阵向量乘法

参考文章:稀疏矩阵向量乘法

1、什么是稀疏矩阵向量乘法

稀疏矩阵向量乘(SpMV)把一个稀疏矩阵与一个向量相乘。稀疏矩阵是指矩阵中大部分元素为0的矩阵。这里的向量本身也可是稀疏的,但通常情况下是密集的。

作为一种通用的运算,在科学应用、经济模型、数据挖掘、信息检索中广泛应用。例如,在利用迭代法求解稀疏线性方程组和特征值的问题。同时,也被应用于网页搜索排名和计算机视觉(图像重构等)。

2、稀疏矩阵的储存格式——Compressed Sparse Row(CSR)

虽然矩阵能直观地展现数据间的联系关系,但用矩阵存储图往往会造成极大的空间浪费。所以,采用CSR是计算机存储稀疏矩阵的有效方法。不仅如此,CRS 结构避免存储矩阵中的0值,虽然数值数组不保存0,但是列索引数组和行指针数组作为标记信息,表示了矩阵的形态。

但是,CRS对矩阵的稀疏性没有要求,可以适用于任何矩阵。作为一种针对矩阵的通用方法【CRS 广泛用于大型的矩阵但是仅仅有少量的非零元素(少于10%或者更低)】,但不见得是最高效的。CRS结构也不见得是表示稀疏矩阵最高效的方式,其他稀疏矩阵表示方法也在被使用。

CSR包含三个数组(所有的index从0开始):

1、values(值):用来存储矩阵中的非零元素的值;
2、columnlndex(列索引):第i个元素记录了values[i]元素的列数;
3、rowPtr(行索引): 第i个元素记录了前i-1行包含的非零元素的数量。【注】注意,如果当前行没有非0元素,那么 rowPtr 数组中的值将会重复出现

for example:
在这里插入图片描述

3、稀疏矩阵向量乘法的基本实现及正确性的判定

稀疏矩阵(CSR储存格式)*向量的机理如下所示:

inline void smmSerial(const int numRows,const int *rowIndex,const int
*columnIndex,const float *val,const float *x,float *r1)
{
	int rowStart;
	int rowEnd;
	for(int i=0;i<numRows;i++)
	{
		rowStart = rowIndex[i];
		rowEnd = rowIndex[i+1];
		float sum = 0.0f;
		for(int j=rowStart;j<rowEnd;j++)
		{
			sum += val[j] * x[columnIndex[j]];
		}
		r1[i] = sum;
	}
}

testbench:
将之与普通矩阵*向量的结果进行比对

#include<stdio.h>
const static int SIZE=4;

inline void smmSerial(const int numRows,const int *rowIndex,const int
*columnIndex,const float *val,const float *x,float *r1);

inline void matrixvector(const int numRows,const float M[SIZE][SIZE],const float *x,float *r2);

 int main()
 {
 	float M[SIZE][SIZE]={{3,4,0,0},{0,5,9,0},{2,0,3,1},{0,4,0,6}};//初始矩阵形态 
 	float x[SIZE]={1,2,3,4};//被乘向量 
 	float r1[SIZE];// ①的结果 
 	float val[]={3,4,5,9,2,3,1,4,6};// CSR的values 
 	int columnIndex[] = {0,1,1,2,0,2,3,1,3};//CSR的 columnlndex
	int rowIndex[] = {0,2,4,7,9};//CSR的 rowIndex
	float r2[SIZE];//②的结果 

	smmSerial(SIZE,rowIndex,columnIndex,val,x,r1);
	matrixvector(SIZE,M,x,r2);
	
	//检测正确性
	int fail=0;
	 for(int i=0; i<SIZE; i++)
		if(r1[i]!=r2[i])
			fail = 1;
	if(fail == 1)
		printf("FAILED\n");
	else
		printf("PASS\n");
	return 0;
 }

inline void smmSerial(const int numRows,const int *rowIndex,const int
*columnIndex,const float *val,const float *x,float *r1)
{
	int rowStart;
	int rowEnd;
	for(int i=0;i<numRows;i++)
	{
		rowStart = rowIndex[i];
		rowEnd = rowIndex[i+1];
		float sum = 0.0f;
		for(int j=rowStart;j<rowEnd;j++)
		{
			sum += val[j] * x[columnIndex[j]];
		}
		r1[i] = sum;
	}
}
inline void matrixvector(const int numRows,const float M[SIZE][SIZE],const float *x,float *r2)
{
	for(int i=0;i<numRows;i++)
	{
		float sum=0.0f;
		for(int j=0;j<numRows;j++)
		{
			sum+=M[i][j]*x[j];
		}
		r2[i]=sum;
	}
}

结果
在这里插入图片描述
但由于上述例子数据量过小,无法对比程序运行时间clock()。可以加大矩阵的SIZE进行比较,并通过随机数生成许多组测试数据rand()

	float M[SIZE][SIZE],x[SIZE],val[SIZE*SIZE];
	int columnIndex[SIZE*SIZE],rowIndex[SIZE+1];
	int k=0;
	//创建原矩阵M与三个向量 
	rowIndex[0]=0;
	for(int i=0;i<SIZE;i++)
	{
		for(int j=0;j<SIZE;j++)
		{
			M[i][j]=rand()%10;
			if(M[i][j]==0)continue;
			val[k]=M[i][j];
			columnIndex[k]=j;
			k++;
		}
		rowIndex[i+1]=k;
	}
	// 创建被乘向量 
	for(int i=0;i<SIZE;i++)
	{
		x[i]=rand()%10;
	}

4、优化

4.1、OpenMP

采用OpenMP,在for循环前添加 #pragma omp parallel for schedule(dynamic,chunksize)private(rowStart,rowEnd)

inline void smmSerial(const int numRows,const int *rowIndex,const int
*columnIndex,const float *val,const float *x,float *r1)
{
	int rowStart;
	int rowEnd;
	#pragma omp parallel for schedule(dynamic,chunksize)private(rowStart,rowEnd)
	for(int i=0;i<numRows;i++)
	{
		rowStart = rowIndex[i];
		rowEnd = rowIndex[i+1];
		float sum = 0.0f;
		for(int j=rowStart;j<rowEnd;j++)
		{
			sum += val[j] * x[columnIndex[j]];
		}
		r1[i] = sum;
	}
}
  • private(rowStart,rowEnd)私有化使内存更加局域化,也就是每个线程都有自己的rowStart和rowEnd,通过减少竞争的方式来加快运行速度

  • 由于每行非零元素个数可能差异巨大,为了减少负载均衡的问题,采用dynamic

  • 同时chunksize的大小也需要考虑,若循环结构中循环次数为100次,通过schedule队列指定每块尺寸(chunksize)为20(即20个循环),则有5个并行块,每个并行块中并形体(一个循环)仍然按串行方式排列。每个并行块对应一个新的线程。若计算机CPU具有4个执行核或4线程,那么每一时刻最多只能执行4个线程,而现在有5个并行块,所以最多只能执行4个并行块,剩下一个并行块就只有在后面,并行计算所耗时间是由最慢的那个线程(并行块)来决定的,所以尽星让这些并行块数目是计算机CPU核心的倍数(1倍或其它整数倍),以充分利用计算机CPU资源

具体详解见我的另一篇文章:OpenMP并行编程学习

4.2、HLS

(待续)
参考文章:
了解Vivado HLS
【HLS】 数组接口综合 优化

inline void smmSerial(const int numRows,const int *rowIndex,const int
*columnIndex,const float *val,const float *x,float *r1)
{
	int rowStart;
	int rowEnd;
	for(int i=0;i<numRows;i++)
	{
		rowStart = rowIndex[i];
		rowEnd = rowIndex[i+1];
		float sum = 0.0f;
		for(int j=rowStart;j<rowEnd;j++)
		{
			#pragma HLS unroll factor=8
			#pragma HLS pipeline
			sum += val[j] * x[columnIndex[j]];
		}
		r1[i] = sum;
	}
}
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值