通用矩阵乘算法从入门到实践

本文涉及的产品
文本翻译,文本翻译 100万字符
图片翻译,图片翻译 100张
语种识别,语种识别 100万字符
简介: 通用矩阵乘算法从入门到实践

一,背景知识

实践作业1:如何通过工具得到自己当前使用 PCCache 信息(15分)。包括 L1/L2/L3 Cache(数据和指令)的大小,Cache Line 的大小。

1.1,Linux 查看 CPU 和 Cache 信息

1,Linux 查看 cpu 信息命令:cat /proc/cpuinfo

(base) harley@harley-pc:/sys/devices/system/cpu/cpu0/cache/index3$ cat /proc/cpuinfo
processor    : 0
vendor_id    : GenuineIntel
cpu family    : 6
model        : 158
model name    : Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
stepping    : 10
microcode    : 0xb4
cpu MHz        : 3192.005
cache size    : 12288 KB
physical id    : 0
siblings    : 1
core id        : 0
cpu cores    : 1
apicid        : 0
initial apicid    : 0
fpu        : yes
fpu_exception    : yes
cpuid level    : 22
wp        : yes
flags        : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xsaves arat md_clear flush_l1d arch_capabilities
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds
bogomips    : 6384.01
clflush size    : 64
cache_alignment    : 64
address sizes    : 43 bits physical, 48 bits virtual
power management:
...
processor    : 3
...
复制代码


2, Linux 查询 L1/L2/L3 cache大小:cat /sys/devices/system/cpu/cpu0/cache/index*/size(*0/1/2/3)

(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index1/type
Instruction
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index1/size
32K
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index2/size
256K
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index3/size
12288K
复制代码


现代 CPUL1 cache 是逻辑核私有的,L1 cache 分指令 L1 cache 和数据 L1 cache,大小相等都为 32 KB;目前,L2 cache 也是片内私有,所以每个核只有256 KB;而对于 L3 cache,一个物理核CPU 的所有逻辑核共享,所以在每个逻辑核来看,L3 cache 都为12288 KB。本机的虚拟机总共有 1 个物理核,而本机共有 612 线程,所以可推算得到**本机的 Cache 信息:

  • L1 cache:
  • L1 Data: 192 KB = 32 x 6 KB
  • L1 Instruction: 192 KB = 32 x 6 KB
  • L2 cache:1536 KB = 256 X 6 KB
  • L3 cache:12288 KB

1.2,Windows查看cpu和cache信息

1,任务管理器->性能,即可查看 cpuL1/L2/L3 Cache 大小,如下图所示。

网络异常,图片无法展示
|


2,或者下载安装 cpuz 软件,打开即可查看,如下图所示。

网络异常,图片无法展示
|


1.3, 尝试分析 init() 函数

实践作业2:尝试分析 init()函数使用 O1O3 优化的profile结果差异。

  • O3 相对 O1,执行时间减少,尝试分析反汇编代码(O3.s 和 O1.s),给出解释(15 分)
  • O3 相对 O1,D1 Cache 的访问次数为什么从 8409K 下降到 2125K(15 分)
  • O3 相对 O1,D1 Cache 的 miss rate 为什么从 6.2% 上升到 24.7%(15 分)

问题分析和答案:

  1. 使用 -O3 参数优化,编译器会采取很多向量化算法,提高代码的并行执行程度,利用现代 CPU 中的流水线,Cache 等。O3 优化会提高执行代码的大小,也会降低目标代码的执行时间。
  2. 访问次数下降是因为使用了 O3 优化,使得程序会自动访问连续的内存。
  3. 高速缓存缺失(cache miss)是因为访存的内存都是不连续的。

二,优化矩阵乘法

实践作业3:优化 AT∗AA^T*AATA 的矩阵乘法,目标是尽量减少计算时间。

  • 其中 A 的大小为 1024x8192,元素为 int类型。
  • 需要从算法层面,指令层面和访存优化的角度联合优化。
  • 通过文档说明自己的优化思路(20 分)。
  • 可以选择自己熟悉的处理器平台进行代码编写,如 Intel 平台或者ARM平台(20 分)

问题分析:矩阵乘的算法优化可分为两类:

  • 基于算法分析的方法:根据矩阵乘计算特性,从数学角度优化,典型的算法包括 Strassen 算法和 Coppersmith–Winograd 算法。
  • 基于软件优化的方法:根据计算机存储系统的层次结构特性,选择性地调整计算顺序,主要有循环拆分向量化、内存重排等。

2.1,算法层面优化

从算法层面优化,首先需要分析朴素矩阵乘法的算法复杂度,分析可知,朴素的矩阵乘算法的时间复杂度为 O(n3)O(n^3)O(n3) 。根据矩阵乘计算特性,从数学角度(算法层面)优化,典型的算法包括 Strassen 算法和 Coppersmith–Winograd 算法。

2.1.1,Strassen 算法

Strassen 算法是 1969 年提出的复杂度为 O(nlog27)O(n^{log_2{7}})O(nlog27) 的矩阵乘法,这是历史上第一次将矩阵乘的计算复杂度价格低到 O(n3)O(n^3)O(n3) 以下。

基于分治(Divide and Conquer)的思想,将矩阵 A,B,C∈Rn2×n2A, B, C∈R^{n^2×n^2}A,B,CRn2×n2 分别拆分为更小的矩阵,根据矩阵基本的运算法则,拆分后朴素算法的计算共需要八次小矩阵乘法和四次小矩阵加法计算。Strassen 算法的核心思想是通过引入辅助计算的中间矩阵,再将中间矩阵进行组合得到最后的矩阵,这个过程使用了七次乘法和十八次加法,将矩阵乘的算法复杂度降低到了 O(nlog27)O(n^{log_27})O(nlog27) (递归地运行该算法)。算法的详细推导过程如下:

1,基于分治(Divide and Conquer)的思想,Starssen 算法将矩阵 A, B, C∈Rn2×n2A,\ B,\ C \in R^{n^2 \times n^2}A,B,CRn2×n2 分别拆分为更小的矩阵:

A=[A1,1A1,2A2,1A2,2],B=[B1,1B1,2B2,1B2,2],C=[C1,1C1,2C2,1C2,2]\mathbf{A} = \begin{bmatrix} \mathbf{A}_{1,1} & \mathbf{A}_{1,2} \\ \mathbf{A}_{2,1} & \mathbf{A}_{2,2} \end{bmatrix}, \mathbf{B} = \begin{bmatrix} \mathbf{B}_{1,1} & \mathbf{B}_{1,2} \\ \mathbf{B}_{2,1} & \mathbf{B}_{2,2} \end{bmatrix}, \mathbf{C} = \begin{bmatrix} \mathbf{C}_{1,1} & \mathbf{C}_{1,2} \\ \mathbf{C}_{2,1} & \mathbf{C}_{2,2} \end{bmatrix}A=[A1,1A2,1A1,2A2,2],B=[B1,1B2,1B1,2B2,2],C=[C1,1C2,1C1,2C2,2]

其中,Ai,j, Bi,j, Ci,j∈R2n−1×2n−1A_{i,j},\ B_{i,j},\ C_{i,j} \in R^{2^{n-1} \times 2^{n-1}}Ai,j,Bi,j,Ci,jR2n1×2n1。拆分后朴素算法的计算如下所示,共需要八次小矩阵乘法和四次小矩阵加法计算。

网络异常,图片无法展示
|


2,引入七个如下所示的用于辅助计算的中间矩阵。

网络异常,图片无法展示
|


3,将中间矩阵进行组合得到最后的结果矩阵。

网络异常,图片无法展示
|


2.1.2,Coppersmith–Winograd 算法

Strassen 算法尽管学术意义重大,但实际应用有限,Coppersmith–Winograd 算法(1990年)的提出将矩阵乘法的算法复杂度降低到了O(n2.376)O(n^2.376)O(n2.376)。其算法的详细推导过程可参考 Matrix multiplication via arithmetic progressions (原始论文)

2.2,指令层面优化

改进访存局部性和利用向量指令等方法都是属于软件优化方法。软件优化方法基于对计算机体系机构和软件系统的特征分析,结合具体计算的特性,设计出针对性的优化方法。

现在的 CPU 处理器,基本上想获得高的性能,必须要用向量化指令,不管是老的 SSE2AVX 或者 AVX 2.0 等,对于CPU 的优化,如果想达到高性能,必须要用到单指令多数据(SIMD)的向量化指令。

2.3,访存优化

程序运行环境(g++ 编译器基础上 Linux系统比 Windows 运行程序时间更少一些):

  • 操作系统:Ubuntu
  • 编译器:g++g++ --std=c++17 -O3 matrix_multiplication.cpp
  • 编程语言:C++
  • CPU平台: IntelI7-8700CPU

朴素的矩阵乘算法的时间复杂度为 O(n3)O(n^3)O(n3),以 AT∗AA^T*AATA 为例,矩阵相乘核心代码如下:

vector<vector<int>> matrix_mul(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘函数
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int new_rows = A.size();
    int new_cols = (*B.begin()).size();
    int L = B.size();
    vector<vector<int>> C(new_rows, vector<int>(new_cols,0));
    for(int i=0; i<new_rows; i++){
        for(int j=0; j<new_cols;j++){
           for(int k=0;k<L;k++){
              C[i][j] += A[i][k]*B[k][j]
           }
            // C[i][j] = vector_mul(A[i], get_col(B, j));
        }
    }
    return C;
}
复制代码


从以上代码可以看出,B[k][j] 读取内存中的数据,是不连续的。在最底层的循环中,随着 k 不断加 1B[k][j] 不断的在内存中跳跃。这会引起缓存命中率低,循环程序不断的把内存转移至缓存,引起效率降低。在我的台式机的虚拟机上,当A 的大小为 1024x8192时,需要用时 85.3 s(我用的编译器是g++ -O3)。下面的代码是我从访存优化的角度使用的两种优化方法。

2.3.1,优化方法 1 (改进访存局部性)

内存使用上,程序访问的内存地址之间连续性越好,程序的访问效率就越高。

充分利用计算机系统的特性可以大幅度提高程序性能,参考卡内基梅隆大学的镇校神课《深入理解计算机系统》里面,给出一种方法,仅仅改变循环的次序,就可以大幅度提高性能,修改后代码如下:

vector<vector<int>> matrix_mul_optim(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘函数
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int new_rows = A.size();
    int new_cols = (*B.begin()).size();
    int L = B.size();
    vector<vector<int>> C(new_rows, vector<int>(new_cols,0));
    for(int k=0; k<L; k++){
        for(int i=0; i<new_rows; i++){
            int r = A[i][k];
            for(int j=0; j<new_cols;j++){
                C[i][j] += A[i][k]*B[k][j];
            }
            // C[i][j] = vector_mul(A[i], get_col(B, j));
        }
    }
    return C;
}
复制代码


首先,最内层的循环,随着 j1C[i][j]B[k][j] 都是每次只加 1,这符合空间局部性的原理,也就是说,内存每次读取都是一个接着一个的来,没有大幅度跳跃。其次,A[i][k] 在中间层循环是跳跃的,但是中间层执行的没有底层那么多,而且我们把 A[i][k] 赋给了局部变量 r,在编译器生成汇编代码的过程中,局部变量 r 应该由 CPU 寄存器存储,最底层循环程序读取寄存器的时间几乎可以忽略不计的。修改后的代码运行耗时 25.2 s

2.3.2,优化方法2(分块矩阵+改进访存局部性)

将矩阵分块(计算拆分),每次计算一部分内容。分块的目的就是优化访存,通过分块之后让访存都集中在一定区域,能够提高了数据局部性,从而提高 Cache 利用率,性能就会更好。结合分块矩阵和改进访存局部性两种方法的代买运行耗时 18.8 s。修改后的代码如下:

vector<vector<int>> matrix_mul_optim3(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘函数,优化方法-分块矩阵
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int M = A.size();
    int N = (*B.begin()).size();
    int K = B.size();  // 第二个矩阵的行
    int NUM = 8;  // 分块数
    int MT = A.size()/NUM;  // 分块矩阵的行
    int NT = (*B.begin()).size()/NUM;  // 分块矩阵的列
    int KT = B.size()/NUM;  // 
    vector<vector<int>> C(M, vector<int>(N,0));
    for(int kt = 0; kt < NUM; ++kt){
        for(int it = 0; it < NUM; ++it){
            for(int jt = 0; jt < NUM; ++jt){
                int ktt = kt * KT;
                int itt = it * MT;
                int jtt = jt * NT;
                for(int k = ktt; k < ktt + KT; ++k){
                    int num_k = k * NUM;
                    for(int i = itt; i < itt + MT; ++i){
                        // int num_i = i * NUM;
                        int r = A[i][k];
                        for(int j = jtt; j < jtt + NT; ++j){
                            C[i][j] += r * B[k][j];
                        }
                    }
                }
            }
        }
    }
    return C;
}
复制代码


程序输出结果如下:

Done! Timing : 18.889000 s The size of result matrix is (8192, 8192)

三,优化方法集合的完整代码

完整可直接在windows/Linux 上可运行的代码如下:

/*
 * 矩阵乘法(A^T*A)实现
 */
#include<iostream>
#include<stdlib.h>
#include<vector>
#include<cassert>
#include"iomanip"
#include <chrono>
#include <iomanip>
using namespace std::chrono;
using namespace std;
typedef std::vector<int> Row;
typedef std::vector<Row> Matrix;
using namespace std;
int m = 1024;
int n = 8192;
vector<vector<int>> init_matrix(int mm, int nn){
    /*初始化指定行和列的二维矩阵
    */
    int random_integer;
    vector<vector<int>> matrix(mm, vector<int>(nn,0)); // 初始化二维数组matrix为1024*8192,所有元素为0
    // cout << "The size of init matrix is " << "(" << matrix.size() << ", " << matrix[0].size() << ")" << std::endl;
    for (int i=0;i < matrix.size();i++)
    {
        for(int j=0;j < matrix[i].size();j++)
        {
            random_integer = rand() % 128;
            matrix[i][j] = random_integer;  // 利用下标给二维数组赋值
            // matrix[i].push_back(random_integer);  // 利用push_back给vactor添加元素
            // cout << random_integer << endl;
        }
    }
    return matrix;
}
void print_matrix(vector<vector<int>> matrix){
    /*打印二维向量(矩阵)的元素
    */
    cout << "The size of matrix is" << "(" << matrix.size() << ", " << matrix[0].size() << ")" << std::endl;
    //迭代器遍历
    // vector<vector<int >>::iterator iter;
    for(auto iter=matrix.cbegin();iter != matrix.cend(); ++iter)
    {
        for(int i = 0;i<(*iter).size();i++){
            cout << (*iter)[i] << " ";
        }
        cout << std::endl;
    }
    // cout << "print success" << endl;
}
vector<vector<int>> matrix_transpose(vector<vector<int>> A){
    /*获取矩阵的转置
    */
    int rows = A.size();
    int cols = (*A.begin()).size();
    vector<vector<int>> A_T(cols, vector<int>(rows,0));
    for (int j=0;j < cols; j++){
        for(int i=0;i< rows; i++){
            A_T[j][i] = A[i][j];
        }
    }
    return A_T;
}
int vector_mul(vector<int> A1, vector<int> B1){
    /*向量相乘函数
    */
    assert(A1.size()==B1.size()); //断言,两个向量的长度必须相等
    vector<int>::iterator begin;  // 定义迭代器
    int result;
    // 迭代器循环遍历元素
    for(int i=0; i<A1.size(); i++){
        result += A1[i]*B1[i];
    }
    return result;
}
vector<int> get_col(vector<vector<int>> matrix, int n){
    /*获取矩阵指定列的向量
    */
//    vector<int> col(matrix.size());
    vector<int> col;
    col.reserve(matrix.size());
    for(auto row: matrix){
        col.push_back(row[n]);
    }
    // cout << "The size of vector is " << col.size() << endl;
    return col;
}
vector<vector<int>> matrix_mul(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘函数
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int new_rows = A.size();
    int new_cols = (*B.begin()).size();
    int L = B.size();
    vector<vector<int>> C(new_rows, vector<int>(new_cols,0));
    for(int i=0; i<new_rows; i++){
        for(int j=0; j<new_cols;j++){
            for(int k=0; k<L; k++){
                C[i][j] += A[i][k]*B[k][j];
            }
            // C[i][j] = vector_mul(A[i], get_col(B, j));
        }
    }
    return C;
}
vector<vector<int>> matrix_mul_optim1(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘优化函数1-改进访存局部性
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int new_rows = A.size();
    int new_cols = (*B.begin()).size();
    int L = B.size();
    vector<vector<int>> C(new_rows, vector<int>(new_cols,0));
    for(int k=0; k<L; k++){
        for(int i=0; i<new_rows; i++){
            int r = A[i][k];
            for(int j=0; j<new_cols;j++){
                C[i][j] += r * B[k][j];
            }
            // C[i][j] = vector_mul(A[i], get_col(B, j));
        }
    }
    return C;
}
vector<vector<int>> matrix_mul_optim2(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘优化函数2-计算拆分
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int M = A.size();
    int N = (*B.begin()).size();
    int K = B.size();  // 第二个矩阵的行
    vector<vector<int>> C(M, vector<int>(N,0));
    for(int m = 0; m < M; m += 4){
        for(int n = 0; n < N; n += 4){
            C[m + 0][n + 0] = 0;
            C[m + 0][n + 1] = 0;
            C[m + 0][n + 2] = 0;
            C[m + 0][n + 3] = 0;
            C[m + 1][n + 0] = 0;
            C[m + 1][n + 1] = 0;
            C[m + 1][n + 2] = 0;
            C[m + 1][n + 3] = 0;
            C[m + 2][n + 0] = 0;
            C[m + 2][n + 1] = 0;
            C[m + 2][n + 2] = 0;
            C[m + 2][n + 3] = 0;
            C[m + 3][n + 0] = 0;
            C[m + 3][n + 1] = 0;
            C[m + 3][n + 2] = 0;
            C[m + 3][n + 3] = 0;
            for(int k = 0;k < K; k +=4){
                //**********************************************//
                C[m + 0][n + 0] += A[m + 0][k + 0] * B[k][n + 0];
                C[m + 0][n + 0] += A[m + 0][k + 1] * B[k][n + 0];
                C[m + 0][n + 0] += A[m + 0][k + 2] * B[k][n + 0];
                C[m + 0][n + 0] += A[m + 0][k + 3] * B[k][n + 0];
                C[m + 0][n + 1] += A[m + 0][k + 0] * B[k][n + 1];
                C[m + 0][n + 1] += A[m + 0][k + 1] * B[k][n + 1];
                C[m + 0][n + 1] += A[m + 0][k + 2] * B[k][n + 1];
                C[m + 0][n + 1] += A[m + 0][k + 3] * B[k][n + 1];
                C[m + 0][n + 2] += A[m + 0][k + 0] * B[k][n + 2];
                C[m + 0][n + 2] += A[m + 0][k + 1] * B[k][n + 2];
                C[m + 0][n + 2] += A[m + 0][k + 2] * B[k][n + 2];
                C[m + 0][n + 2] += A[m + 0][k + 3] * B[k][n + 2];
                C[m + 0][n + 3] += A[m + 0][k + 0] * B[k][n + 3];
                C[m + 0][n + 3] += A[m + 0][k + 1] * B[k][n + 3];
                C[m + 0][n + 3] += A[m + 0][k + 2] * B[k][n + 3];
                C[m + 0][n + 3] += A[m + 0][k + 3] * B[k][n + 3];
                //**********************************************//
                C[m + 1][n + 0] += A[m + 1][k + 0] * B[k][n + 0];
                C[m + 1][n + 0] += A[m + 1][k + 1] * B[k][n + 0];
                C[m + 1][n + 0] += A[m + 1][k + 2] * B[k][n + 0];
                C[m + 1][n + 0] += A[m + 1][k + 3] * B[k][n + 0];
                C[m + 1][n + 1] += A[m + 1][k + 0] * B[k][n + 1];
                C[m + 1][n + 1] += A[m + 1][k + 1] * B[k][n + 1];
                C[m + 1][n + 1] += A[m + 1][k + 2] * B[k][n + 1];
                C[m + 1][n + 1] += A[m + 1][k + 3] * B[k][n + 1];
                C[m + 1][n + 2] += A[m + 1][k + 0] * B[k][n + 2];
                C[m + 1][n + 2] += A[m + 1][k + 1] * B[k][n + 2];
                C[m + 1][n + 2] += A[m + 1][k + 2] * B[k][n + 2];
                C[m + 1][n + 2] += A[m + 1][k + 3] * B[k][n + 2];
                C[m + 1][n + 3] += A[m + 1][k + 0] * B[k][n + 3];
                C[m + 1][n + 3] += A[m + 1][k + 1] * B[k][n + 3];
                C[m + 1][n + 3] += A[m + 1][k + 2] * B[k][n + 3];
                C[m + 1][n + 3] += A[m + 1][k + 3] * B[k][n + 3];
                //**********************************************//
                C[m + 2][n + 0] += A[m + 2][k + 0] * B[k][n + 0];
                C[m + 2][n + 0] += A[m + 2][k + 1] * B[k][n + 0];
                C[m + 2][n + 0] += A[m + 2][k + 2] * B[k][n + 0];
                C[m + 2][n + 0] += A[m + 2][k + 3] * B[k][n + 0];
                C[m + 2][n + 1] += A[m + 2][k + 0] * B[k][n + 1];
                C[m + 2][n + 1] += A[m + 2][k + 1] * B[k][n + 1];
                C[m + 2][n + 1] += A[m + 2][k + 2] * B[k][n + 1];
                C[m + 2][n + 1] += A[m + 2][k + 3] * B[k][n + 1];
                C[m + 2][n + 2] += A[m + 2][k + 0] * B[k][n + 2];
                C[m + 2][n + 2] += A[m + 2][k + 1] * B[k][n + 2];
                C[m + 2][n + 2] += A[m + 2][k + 2] * B[k][n + 2];
                C[m + 2][n + 2] += A[m + 2][k + 3] * B[k][n + 2];
                C[m + 2][n + 3] += A[m + 2][k + 0] * B[k][n + 3];
                C[m + 2][n + 3] += A[m + 2][k + 1] * B[k][n + 3];
                C[m + 2][n + 3] += A[m + 2][k + 2] * B[k][n + 3];
                C[m + 2][n + 3] += A[m + 2][k + 3] * B[k][n + 3];
                //**********************************************//
                C[m + 3][n + 0] += A[m + 3][k + 0] * B[k][n + 0];
                C[m + 3][n + 0] += A[m + 3][k + 1] * B[k][n + 0];
                C[m + 3][n + 0] += A[m + 3][k + 2] * B[k][n + 0];
                C[m + 3][n + 0] += A[m + 3][k + 3] * B[k][n + 0];
                C[m + 3][n + 1] += A[m + 3][k + 0] * B[k][n + 1];
                C[m + 3][n + 1] += A[m + 3][k + 1] * B[k][n + 1];
                C[m + 3][n + 1] += A[m + 3][k + 2] * B[k][n + 1];
                C[m + 3][n + 1] += A[m + 3][k + 3] * B[k][n + 1];
                C[m + 3][n + 2] += A[m + 3][k + 0] * B[k][n + 2];
                C[m + 3][n + 2] += A[m + 3][k + 1] * B[k][n + 2];
                C[m + 3][n + 2] += A[m + 3][k + 2] * B[k][n + 2];
                C[m + 3][n + 2] += A[m + 3][k + 3] * B[k][n + 2];
                C[m + 3][n + 3] += A[m + 3][k + 0] * B[k][n + 3];
                C[m + 3][n + 3] += A[m + 3][k + 1] * B[k][n + 3];
                C[m + 3][n + 3] += A[m + 3][k + 2] * B[k][n + 3];
                C[m + 3][n + 3] += A[m + 3][k + 3] * B[k][n + 3];
            }
        }
    }
    return C;
}
vector<vector<int>> matrix_mul_optim3(vector<vector<int>> A, vector<vector<int>> B){
    /*二维矩阵相乘优化函数3-(分块矩阵+改进访存局部性)
    */
    // vector<vector<int>> A_T = matrix_transpose(A);
    assert((*A.begin()).size()==B.size()); //断言,第一个矩阵的列必须等于第二个矩阵的行
    int M = A.size();
    int N = (*B.begin()).size();
    int K = B.size();  // 第二个矩阵的行
    int NUM = 8;  // 分块数
    int MT = A.size()/NUM;  // 分块矩阵的行
    int NT = (*B.begin()).size()/NUM;  // 分块矩阵的列
    int KT = B.size()/NUM;  //
    vector<vector<int>> C(M, vector<int>(N,0));
    for(int kt = 0; kt < NUM; ++kt){
        for(int it = 0; it < NUM; ++it){
            for(int jt = 0; jt < NUM; ++jt){
                int ktt = kt * KT;
                int itt = it * MT;
                int jtt = jt * NT;
                for(int k = ktt; k < ktt + KT; ++k){
                    int num_k = k * NUM;
                    for(int i = itt; i < itt + MT; ++i){
                        // int num_i = i * NUM;
                        int r = A[i][k];
                        for(int j = jtt; j < jtt + NT; ++j){
                            C[i][j] += r * B[k][j];
                        }
                    }
                }
            }
        }
    }
    return C;
}
int main(){
    vector<vector<int>> A;
    A = init_matrix(1024,8192);
    auto A_T = matrix_transpose(A);
    // print_matrix(A);
    // print_matrix(A_T);
    auto start = std::chrono::steady_clock::now();  // 开始时间
    auto B = matrix_mul_optim3(A_T, A);
    // print_matrix(B);
    auto end = std::chrono::steady_clock::now();  // 匹配结束后时间
    auto tt = duration_cast < std::chrono::milliseconds > (end - start);
    printf("Done! Timing : %lf s\n", tt.count() / 1000.0);
    cout << "The size of result matrix is " << "(" << B.size() << ", " << B[0].size() << ")" << std::endl;
    return 0;
}
复制代码


程序输出结果如下:

The size of init A matrix is (1024, 8192) Done! Timing : 18.787000 s The size of result matrix is (8192, 8192)

参考资料


相关文章
|
2月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
61 4
|
3月前
|
机器学习/深度学习 人工智能 算法
深度学习入门:理解神经网络与反向传播算法
【9月更文挑战第20天】本文将深入浅出地介绍深度学习中的基石—神经网络,以及背后的魔法—反向传播算法。我们将通过直观的例子和简单的数学公式,带你领略这一技术的魅力。无论你是编程新手,还是有一定基础的开发者,这篇文章都将为你打开深度学习的大门,让你对神经网络的工作原理有一个清晰的认识。
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
2月前
|
机器学习/深度学习 算法 搜索推荐
django调用矩阵分解推荐算法模型做推荐系统
django调用矩阵分解推荐算法模型做推荐系统
31 4
|
2月前
|
机器学习/深度学习 算法 Python
探索机器学习中的决策树算法:从理论到实践
【10月更文挑战第5天】本文旨在通过浅显易懂的语言,带领读者了解并实现一个基础的决策树模型。我们将从决策树的基本概念出发,逐步深入其构建过程,包括特征选择、树的生成与剪枝等关键技术点,并以一个简单的例子演示如何用Python代码实现一个决策树分类器。文章不仅注重理论阐述,更侧重于实际操作,以期帮助初学者快速入门并在真实数据上应用这一算法。
|
2月前
|
机器学习/深度学习 人工智能 Rust
MindSpore QuickStart——LSTM算法实践学习
MindSpore QuickStart——LSTM算法实践学习
40 2
|
2月前
|
机器学习/深度学习 算法
机器学习入门(三):K近邻算法原理 | KNN算法原理
机器学习入门(三):K近邻算法原理 | KNN算法原理
|
2月前
|
机器学习/深度学习 算法 大数据
机器学习入门:梯度下降算法(下)
机器学习入门:梯度下降算法(下)
|
2月前
|
机器学习/深度学习 算法 API
机器学习入门(五):KNN概述 | K 近邻算法 API,K值选择问题
机器学习入门(五):KNN概述 | K 近邻算法 API,K值选择问题
|
2月前
|
机器学习/深度学习 算法 数据建模
计算机前沿技术-人工智能算法-生成对抗网络-算法原理及应用实践
计算机前沿技术-人工智能算法-生成对抗网络-算法原理及应用实践
30 0
下一篇
无影云桌面