1. 请编写两个1024*1024大小的矩阵(矩阵元素为double类型)相乘的程序。要求采用分块相乘的技术以提高效率,并分析分块方式和Cache容量之间的关系。
解:程序如下: // 由于参数发生了修改,请修正答案。
#include <iostream.h>
#include <time.h>
const int block_size=16; //控制矩阵相乘的块的大小
const int array_size=1024;
const int subArray_size=(int)array_size/block_size;
//求得子数组在每行/列的个数;
int temp_array[block_size][block_size];
int array1[array_size][array_size];
int array2[array_size][array_size];
int result[array_size][array_size];
void temp_mul(int x1,int y1,int x2,int y2)//结果放到temp_array[][]中;
{
for(int i=0;i<block_size;i++)
for(intj=0;j<block_size;j++)
{
for(intk=0;k<block_size;k++)
{
temp_array[i][j] +=array1[x1+i][y1+k] * array2[x2+k][y2+j];
}
}
}
void matrix_mul()
{
/*分块相乘
for(int i=0; i<subArray_size; i++)
for(int j=0;j<subArray_size; j++)
{
for(int k=0;k<subArray_size; k++)
{
temp_mul(i*block_size,k*block_size,k*block_size,j*block_size);
for(intl=0;l<block_size;l++)
for(intm=0;m<block_size;m++)
{
result[i*block_size+l][j*block_size+m] += temp_array[l][m];
temp_array[l][m]=0; } } }*/ /*
int temp;
for(int i0=0; i0<array_size; i0++) //完成array2的转置
for(int j0=0; j0<i0; j0++)
{
temp=array2[i0][j0];
array2[i0][j0]=array2[j0][i0];
array2[j0][i0]=temp; }
for(int i1=0; i1<array_size; i1++) //按行相乘
for(int j1=0; j1<array_size;j1++)
{
for(intk1=0;k1<array_size; k1++)
{
result[i1][j1]+=array1[i1][k1]*array2[j1][k1]; } } } */
int temp1;
for(int i2=0; i2<array_size; i2++) //完成array1的转置
for(int j2=0; j2<i2; j2++)
{
temp1=array1[i2][j2];
array1[i2][j2]=array1[j2][i2];
array1[j2][i2]=temp1; }
for(int i3=0; i3<array_size; i3++) //按列相乘
for(int j3=0; j3<array_size;j3++)
{
for(int k2=0;k2<array_size;k2++)
{
result[i3][j3]+=array1[k2][i3]*array2[k2][j3]; }} }
int main(int argc, char* argv[])
{
for(int x=0;x<array_size;x++)
for(inty=0;y<array_size;y++)
{
array1[x][y]=1;
array2[x][y]=2;
result[x][y]=0; }
array1[0][0]=2;
array1[0][1]=4;
array1[1][0]=6;
array1[1][1]=8;
array2[0][0]=3;
array2[0][1]=3;
array2[1][0]=9;
array2[1][1]=27;
clock_t start, finish;
double duration;
start=clock();
matrix_mul();
finish=clock();
duration=(double)(finish-start)/CLOCKS_PER_SEC;
cout<<"duration="<<duration<<endl;
cout<<result[4][6]<<endl;//用于验证矩阵计算程序的正确性;
cout<<result[7][3]<<endl;
cout<<result[5][1]<<endl;
cout<<result[2][0]<<endl; }
运行上面的程序,可以通过其中的result矩阵的几个元素result[4][6]=4096;
Result[7][3]=4096; result[5][1]=4122; result[2][0]=4104;从而可以验证了对于分块相乘,按行相乘,按列相乘的程序代码的正确性。
以上代码运行在:Intel (R)Pentium (R)4, 1400MHZ
L1-D cache 64 KB
L2-cache 128KB
运行时间是:
按行相乘是:155.77 S
按列相乘是:1183.05 S
分块大小 | 运行时间(S) |
2 | 769.77 |
4 | 418.79 |
8 | 252.2 |
16 | 200.77 |
32 | 206.69 |
64 | 918.68 |
可见:按行相乘的性能最好;按列相乘的性能最差;按块相乘,当块大小是16的时候性能最好,而无论块更大还是更小,性能都会差一些。
原因分析:
按行相乘时候,无论对于array1还是array2, miss一次而从内存中取到cache中的数据都会被用到,所以cache的使用率最好。 而对于array2求转置仅仅每个元素使用一次,相对于2048的矩阵相乘带来的好处而言,转置引起的miss非常值得。从而总体的miss 次数最少,从而运行时间最少。
而按列相乘恰好相反,首先对array1求转置就需要一定的miss 开销,同时转置对于矩阵相乘而言并没有带来任何的好处,它造成了array1和array2一样,每次随miss元素而取出的元素都没有被用到,所以造成了cache的使用效率是最低的。从而它总体的miss次数最多,这样运行时间也最长。
对于按块相乘而言,当块大小是16的时候,子矩阵一行的大小是:16*4B=64B恰好是我的机器的L1 data cache 的块大小,因为miss一次所取到cache的数据全部立即被用到,因而这时候矩阵相乘cache的使用效率是最高的,miss的次数也是最少的,所以此时运行时间相对于块更大或更小而言是最少的;但是对于块更小而言,一次取来的数据当时不会立即用到,从而之后再用到的时候可能已经被剔除了cache;而当块更大的时候,一次每计算一行都需要造成多次cache的miss,这样,由于cache总大小有限,就会造成计算结果在cache 和内存间的频频移动,所以miss次数也是很大的,而且随着子矩阵的变大,而miss次数也会变大,从而导致了cache 块更大或更小运行时间都会明显增加。