目标:Darknet 源码cpu矩阵乘法函数 gemm_nn 优化。参数说明:lda A的列数; ldb B的列数; ldc C的列数; M C的行数; K A的列数
测试方法:Darknet源码,makefile文件添加编译选项 -pg,编译后得到可执行程序 darknet,运行可执行程序:
| ./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/kite.jpg |
得到文件 gmon.out,转换为文本,查看热点函数及其耗时。
| gprof darknet gmon.out > out |
- 矩阵乘法基础写法
| for(int i=0; i<M; i++) |
| { |
| for(int j=o; j<N; j++) |
| { |
| for(int k=0; k<K; k++) |
| { |
| C[i][j] += A[i][k] * B[k][j]; |
| } |
| } |
| } |
- darknet 矩阵乘法源码 src/gemm.c
| void gemm_nn(int M, int N, int K, float ALPHA, float *A, int lda, float*B, int ldb,loat *C, int ldc) |
| { |
| int i,j,k; |
| for (i = 0; i < M; ++i) |
| { |
| for (j = 0; j < N; ++j) |
| { |
| register float c_sum = 0.0f; |
| for (k = 0; k < K; ++k) |
| { |
| |
| c_sum += ALPHA * A[i * lda + k] * B[k * ldb + j ]; |
| } |
| |
| C[i * ldc +j] += c_sum; |
| } |
| } |
| } |
- 循环展开优化:2x和4x加速不明显
| void gemm_nn(int M, int N, int K, float ALPHA, |
| float *A, int lda, |
| float *B, int ldb, |
| float *C, int ldc) |
| { |
| int i,j,k; |
| for (i = 0; i < M; ++i) |
| { |
| for (j = 0; j < N; ++j) |
| { |
| register float c_sum = 0.0f; |
| #if 0 |
| for (k = 0; k < K; ++k) |
| { |
| c_sum += ALPHA * A[i * lda + k] * B[k * ldb + j]; |
| } |
| #elif 1 |
| for (k = 0; k < K; k += 2) |
| { |
| c_sum += ALPHA * A[i * lda + k] * B[k * ldb + j]; |
| c_sum += ALPHA * A[i * lda + k+1] * B[(k+1) * ldb + j]; |
| } |
| #elif |
| for (k = 0; k < K; k += 4) |
| { |
| c_sum += ALPHA * A[i * lda + k] * B[k * ldb + j]; |
| c_sum += ALPHA * A[i * lda + k+1] * B[(k+1) * ldb + j]; |
| c_sum += ALPHA * A[i * lda + k+2] * B[(k+2) * ldb + j]; |
| c_sum += ALPHA * A[i * lda + k+3] * B[(k+3) * ldb + j]; |
| } |
| #elif |
| #endif |
| C[i * ldc + j] += c_sum; |
| } |
| } |
| } |
- 矩阵乘法一维分块优化
| void gemm_nn(int M, int N, int K, float ALPHA, |
| float *A, int lda, |
| float *B, int ldb, |
| float *C, int ldc) |
| { |
| memset(C,0,sizeof(float)*ldc*M); |
| for(int m=0; m<M; m += 4) |
| { |
| for(int n=0; n<N; n += 4) |
| { |
| for(int k=0; k<K; k++) |
| { |
| for(int mb=0; mb<4; mb++) |
| { |
| for(int nb=0; nb<4; nb++) |
| { |
| C[(m+mb)*ldc + (n+nb)] += ALPHA* A[(m+mb)*lda + (k)]*B[(k)*ldb + (n+nb)]; |
| } |
| } |
| } |
| } |
| } |
| } |
- gemm_nn 矩阵乘法二维分块优化:cannon 算法
| void gemm_nn(int M, int N, int K, float ALPHA, |
| float *A, int lda, |
| float *B, int ldb, |
| float *C, int ldc) |
| { |
| memset(C,0,sizeof(float)*ldc*M); |
| for(int m=0; m<M; m += 4) |
| { |
| for(int n=0; n<N; n += 4) |
| { |
| for(int k=0; k<K; k += 4) |
| { |
| for(int mb=0; mb<4; mb++) |
| { |
| for(int nb=0; nb<4; nb++) |
| { |
| for(int kb=0; kb<4; kb++) |
| { |
| C[(m+mb)*ldc + (n+nb)] = ALPHA * A [(m+mb)*lda + (k+kb)] * B[(k+kb)*ldb + (n+nb)]; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
- 对B转置计算
通常矩阵元素的计算公式:
优化后的矩阵元素计算公式:
代码实现:
| void gemm_nn(int M, int N, int K, float ALPHA, float *A, int lda, float*B, int ldb,loat *C, int ldc) |
| { |
| |
| memset(C,0,sizeof(float)*ldc*M); |
| float* Btemp=calloc(K*ldb,sizeof(float)); |
| for(int i=0;i<K;i++) |
| { |
| for(int j=0;j<ldb;j++) |
| Btemp[j*K+i] = B[i*ldb+j]; |
| } |
| for(int m=0;m<M;m++) |
| { |
| for(int n=0;n<ldb;n++) |
| { |
| for(int k=0;k<K;k++) |
| C[m*ldc+n] += ALPHA * A[m*lda+k] * Btemp[n*K+k]; |
| } |
| } |
| free(Btemp); |
| } |
- 测试结果
| gemm_nn 函数耗时: |
| darknet 源码 11.7s |
| loop 2x循环展开:11.5s |
| loop 4x循环展开:12.1s |
| 一维分块4*K:9.2,比源码提升 27 |
| 二维分块block 4*4 :6.53s,比源码效率提升 79 |
| 对B转置:5.17s,比源码提升 126 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?