利用多线程实现N*N的矩阵乘法程序的加速

windows.h库实现多线程

单线程矩阵乘法

每次矩阵乘法计算过程包含一个三重循环过程,这将带来 O($ n^3 $) 的时间复杂度

#单线程实现N*N矩阵乘法
def mul(A=[],B=[]):
    n=len(A)
    C=[[0]*n for row in range(n)]
    for m in range(n):
        for i in range(n):
            for j in range(n):
                C[m][i]+=A[i][j]*B[j][i]
    return C;

多线程矩阵乘法

矩阵乘法是一个重复操作过程,即将矩阵 A 的不同行与矩阵 B 的不同列相乘,且各个相乘结果之间不存在互相依赖关系,因此可以利用多线程计算来对矩阵乘法的计算过程进行加速,理论上,采用nxn个线程时,可以达到 O(n) 的性能

代码实现

#include <windows.h>
#include <iostream>
#include <ctime>
#define N 1000 //将矩阵大小固定为1000*1000
#define MAX_THREADS 10
using namespace std;
struct MYDATA //用于传递多个参数给线程函数
{
    int begin, end;
    int* A, * B, * C;
};
DWORD ThreadProc(LPVOID IpParam)//线程函数,用于计算矩阵乘法
{
    MYDATA* pmd = (MYDATA*)IpParam;
    int* A = pmd->A, * B = pmd->B, * C = pmd->C;
    int begin = pmd->begin, end = pmd->end;
    for (int index = begin; index < end; index++)
    {
        int i = index / N, j = index % N;
        C[i * N + j] = 0;
        for (int n = 0; n < N; n++)
        {
            C[i * N + j] += A[i * N + n] * B[n * N + j];
        }
    }
    return 0;
}
int A[N * N], B[N * N], C[N * N], revB[N * N];
double rate[MAX_THREADS];
double Rate;
int main()
{
    for (int i = 0; i < N * N; i++)
    {
        A[i] = rand();
        B[i] = rand();
    }
    int threads[MAX_THREADS] = {1, 2, 4, 8,16,32,64,100,500,1000 };
    for (int i = 0; i < MAX_THREADS; i++)
    {
        int m = threads[i];
        clock_t start, end;
        start = clock();
        HANDLE hThread[N];//初始化临界区
        static MYDATA mydt[N];
        int temp = (N * N) / m;
        for (int i = 0; i < m; i++)//创建线程
        {
            mydt[i].A = A, mydt[i].B = B, mydt[i].C = C;
            mydt[i].begin = i * temp, mydt[i].end = i * temp + temp;
            if (i == m - 1)//最后一个线程
            {
                mydt[i].end = N * N;
            }
            hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, &mydt[i], 0, NULL);
        }
        WaitForMultipleObjects(m, hThread, TRUE, INFINITE);
        end = clock();
        cout << m << "个线程时运行时间为" << (double)(end - start) / CLOCKS_PER_SEC << "s" << endl;
        rate[i] = (double)(end - start);
        Rate = rate[0] / rate[i];
        cout << "加速比:" << Rate << endl;
    }

}

运行结果:

线程数运行时间加速比
13.717 s1
21.282 s2.5081
40.811 s4.58323
80.524 s7.09351
160.427 s8.70492
320.456 s8.15132
640.472 s7.875
1000.011 s337.909
5000.045 s82.6
10000.184 s20.2011

优化

数据类型:内存中二维数组是以行优先进行存储的,因此可以认为一维数组在内存方面和二维数组没区别

数据大小:这里固定为1000*1000的矩阵

cache:

发现在代码块中

 C[i * N + j] += A[i * N + n] * B[n * N + j];

对于矩阵B的访问存在严重的cache命中率问题,因此可以对矩阵B做一个映射,将B[n * N + j]映射到revB[j * N + n]构造矩阵revB代替B进行矩阵乘法,从而提高cahe命中率

for (int index = 0; index < N; index++)
{
    int i = index / N, j = index % N;
    revB[i * N + j] = B[j * N + i];
}·

代码实现

#include <windows.h>
#include <iostream>
#include <ctime>
#define N 1000
#define MAX_THREADS 10
using namespace std;
struct MYDATA //用于传递多个参数给线程函数
{
    int begin, end;
    int* A, * B, * C;
};
DWORD ThreadProc(LPVOID IpParam)//线程函数,用于计算矩阵乘法
{
    MYDATA* pmd = (MYDATA*)IpParam;
    int* A = pmd->A, * B = pmd->B, * C = pmd->C;
    int begin = pmd->begin, end = pmd->end;
    for (int index = begin; index < end; index++)
    {
        int i = index / N, j = index % N;
        C[i * N + j] = 0;
        for (int n = 0; n < N; n++)
        {
           /* C[i * N + j] += A[i * N + n] * B[n * N + j];*/
            C[i * N + j] += A[i * N + n] * B[j * N + n];
        }
    }
    return 0;
}
int A[N * N], B[N * N], C[N * N], revB[N * N];
double rate[MAX_THREADS];
double Rate;
int main()
{
    for (int i = 0; i < N * N; i++)
    {
        A[i] = rand();
        B[i] = rand();
    }
    for (int index = 0; index < N; index++)//对矩阵B做映射
    {
        int i = index / N, j = index % N;
        revB[i * N + j] = B[j * N + i];
    }
    int threads[MAX_THREADS] = {1, 2, 4, 8,16,32,64,100,500,1000 };
    for (int i = 0; i < MAX_THREADS; i++)
    {
        int m = threads[i];
        clock_t start, end;
        start = clock();
        HANDLE hThread[N];//初始化临界区
        static MYDATA mydt[N];
        int temp = (N * N) / m;
        for (int i = 0; i < m; i++)//创建线程
        {
            mydt[i].A = A, mydt[i].B = revB, mydt[i].C = C;
            mydt[i].begin = i * temp, mydt[i].end = i * temp + temp;
            if (i == m - 1)//最后一个线程
            {
                mydt[i].end = N * N;
            }
            hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, &mydt[i], 0, NULL);
        }
        WaitForMultipleObjects(m, hThread, TRUE, INFINITE);
        end = clock();
        cout << m << "个线程时运行时间为" << (double)(end - start) / CLOCKS_PER_SEC << "s" << endl;
        rate[i] = (double)(end - start);
        Rate = rate[0] / rate[i];
        cout << "加速比:" << Rate << endl;
    }
}

运行结果:

线程数运行时间加速比
12.543 s1
21.281 s1.98517
40.702 s3.62251
80.437 s5.81922
160.374 s6.79947
320.383 s6.63969
640.391 s6.50384
1000.013 s195.615
5000…022 s115.591
10000.19 s13.3842

相比优化之前,速度提升明显。

查看加速比,发现采用多线程加速矩阵乘法效果显著,加速比最高可达300以上。

但加速比并未一直随线程数增多而提升,没有达到理论上采用nxn个线程时 O(n) 的性能。

甚至当线程数过多时,所用时间要远远大于单线程(10000个线程耗费20分钟没有跑出结果)。

分析

1.将创建线程的时间统计在内,而创建线程的耗时相比于简单的进行一行乘以一列的乘加运算,耗时要高很多。

2.线程数超过了操作系统所能同时执行的最大数量,因此每个线程都需要操作系统进行调度执行,调度花费的时间要远超执行矩阵行列乘加运算的用时。

3.线程数过多会导致线程之间频繁地切换,从而增加了上下文切换的开销,降低了计算效率
4.查看cpu以及进程内存,可以发现大部分耗费发生在了内存交换上,而不是cpu运算。

结论

多线程加速矩阵乘法可以显著提高计算速率,但是线程数的选择要根据具体情况进行调整,如果线程数过少,则无法充分利用CPU资源;如果线程数过多,则会导致上下文切换频繁,从而降低计算效率

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想去卖防晒油的象龟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值