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

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

一,背景知识

实践作业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)

参考资料


相关文章
|
6月前
|
存储 算法
算法入门:专题二---滑动窗口(长度最小的子数组)类型题目攻克!
给定一个正整数数组和目标值target,找出总和大于等于target的最短连续子数组长度。利用滑动窗口(双指针)优化,维护窗口内元素和,通过单调性避免重复枚举,时间复杂度O(n)。当窗口和满足条件时收缩左边界,更新最小长度,最终返回结果。
|
6月前
|
存储 算法 编译器
算法入门:剑指offer改编题目:查找总价格为目标值的两个商品
给定递增数组和目标值target,找出两数之和等于target的两个数字。利用双指针法,left从头、right从尾向中间逼近,根据和与target的大小关系调整指针,时间复杂度O(n),空间复杂度O(1)。找不到时返回{-1,-1}。
|
6月前
|
存储 算法
算法入门:专题一:双指针(有效三角形的个数)
给定一个数组,找出能组成三角形的三元组个数。利用“两边之和大于第三边”的性质,先排序,再用双指针优化。固定最大边,左右指针从区间两端向内移动,若两短边之和大于最长边,则中间所有组合均有效,时间复杂度由暴力的O(n³)降至O(n²)。
机器学习/深度学习 算法 自动驾驶
1220 0
|
7月前
|
算法 API 数据安全/隐私保护
深度解析京东图片搜索API:从图像识别到商品匹配的算法实践
京东图片搜索API基于图像识别技术,支持通过上传图片或图片URL搜索相似商品,提供智能匹配、结果筛选、分页查询等功能。适用于比价、竞品分析、推荐系统等场景。支持Python等开发语言,提供详细请求示例与文档。
|
9月前
|
机器学习/深度学习 数据采集 算法
你天天听“数据挖掘”,可它到底在“挖”啥?——数据挖掘算法入门扫盲篇
你天天听“数据挖掘”,可它到底在“挖”啥?——数据挖掘算法入门扫盲篇
196 0
|
10月前
|
监控 算法 安全
公司电脑监控软件关键技术探析:C# 环形缓冲区算法的理论与实践
环形缓冲区(Ring Buffer)是企业信息安全管理中电脑监控系统设计的核心数据结构,适用于高并发、高速率与短时有效的多源异构数据处理场景。其通过固定大小的连续内存空间实现闭环存储,具备内存优化、操作高效、数据时效管理和并发支持等优势。文章以C#语言为例,展示了线程安全的环形缓冲区实现,并结合URL访问记录监控应用场景,分析了其在流量削峰、关键数据保护和高性能处理中的适配性。该结构在日志捕获和事件缓冲中表现出色,对提升监控系统效能具有重要价值。
282 1
|
11月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
264 17
|
11月前
|
存储 算法 安全
如何控制上网行为——基于 C# 实现布隆过滤器算法的上网行为管控策略研究与实践解析
在数字化办公生态系统中,企业对员工网络行为的精细化管理已成为保障网络安全、提升组织效能的核心命题。如何在有效防范恶意网站访问、数据泄露风险的同时,避免过度管控对正常业务运作的负面影响,构成了企业网络安全领域的重要研究方向。在此背景下,数据结构与算法作为底层技术支撑,其重要性愈发凸显。本文将以布隆过滤器算法为研究对象,基于 C# 编程语言开展理论分析与工程实践,系统探讨该算法在企业上网行为管理中的应用范式。
293 8
|
11月前
|
存储 监控 算法
基于 C# 时间轮算法的控制局域网上网时间与实践应用
在数字化办公与教育环境中,局域网作为内部网络通信的核心基础设施,其精细化管理水平直接影响网络资源的合理配置与使用效能。对局域网用户上网时间的有效管控,已成为企业、教育机构等组织的重要管理需求。这一需求不仅旨在提升员工工作效率、规范学生网络使用行为,更是优化网络带宽资源分配的关键举措。时间轮算法作为一种经典的定时任务管理机制,在局域网用户上网时间管控场景中展现出显著的技术优势。本文将系统阐述时间轮算法的核心原理,并基于 C# 编程语言提供具体实现方案,以期深入剖析该算法在局域网管理中的应用逻辑与实践价值。
262 5

热门文章

最新文章