1.下载解压Darknet压缩文件,进入安装目录,编辑Makefile,在CFLAGS参数后添加-pg
CFLAGS=-Wall -Wno-unused-result -Wno-unknown-pragmas -Wfatal-errors -fPIC -pg
2.运行make命令,编译darknet程序
3.使用如下命令行参数中的指定文件作为输入。
yhrun -n 1 -p thcp1 ./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/kite.jpg
4.使用gprof工具分析darknet程序,找到时间开销最大的核心子函数。
输入:gprof darknet gmon.out
可以看到耗时最长的是gemm_nn函数,时间11.98秒,占比95.61%
5.找到该函数
vi src/gemm.c
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;
for (k = 0; k < K; ++k)
{
c_sum += ALPHA * A[i * lda + k] * B[k * ldb + j];
}
C[i * ldc + j] += c_sum;
}
}
}
我们可以看到是一个n*n的矩阵乘法
6.要优化gemm_nn函数的性能,可以考虑以下几个方面:
-
矩阵布局优化:优化内存访问模式以提高缓存命中率。可以使用行主序存储矩阵数据,这样可以利用局部性原理,使得连续的内存访问更加高效。
-
并行计算:可以使用多线程或者并行指令集(如SIMD指令)来加速计算。例如,使用OpenMP或CUDA等并行编程技术,在循环层级进行并行化,提高计算效率。
-
循环展开:通过展开内层循环,减少循环迭代次数,从而减少循环控制的开销,并提高指令级并行性。
-
数据复用:通过重用计算中的临时变量,减少内存读写操作,提高数据访问效率。
-
运算指令选择:使用适当的运算指令,如乘法指令、加法指令等,以提高运算速度。可以根据平台的特性选择适合的优化指令。
-
内存对齐:确保输入和输出数据在内存中的对齐,以使访问数据更加高效。
-
编译器优化:使用合适的编译器选项,如优化级别、循环展开选项等,以帮助编译器进行更好的代码优化。
那么算法本身诞生这么多年了,算法层面不可能让你捡漏,所以基于硬件优化是唯一选择,接下来我们使用循环分块提高缓存命中率的方式优化。
7.首先我们查一下cpu的cache大小,命令lscpu
我们可以看到,1级缓存2mib,那么矩阵乘法1个float类型的数据占4个字节,存放字节最大可达2*1024*1024/4=2*512*512
下面是优化代码
#define block_size 1024
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,ii,jj,kk;
for (i = 0; i < M; i += block_size)
{
for (j = 0; j < N; j += block_size)
{
for (k = 0; k < K; k += block_size)
{
int block_end_i = i + block_size < M ? i + block_size : M;
int block_end_j = j + block_size < N ? j + block_size : N;
int block_end_k = k + block_size < K ? k + block_size : K;
for (ii = i; ii < block_end_i; ++ii)
{
for (jj = j; jj < block_end_j; ++jj)
{
register float c_sum = 0.0f;
for (kk = k; kk < block_end_k; ++kk)
{
c_sum += ALPHA * A[ii * lda + kk] * B[kk * ldb + jj];
}
C[ii * ldc + jj] += c_sum;
}
}
}
}
}
}
我们分别测试block_size为1024,512,128,64,32时的计算速度
1024,时间来到11秒
512,时间逼近7秒
256,逼近4秒
128,时间逼近3秒
64,还是3秒,看来最佳块大小在64-128之间
我们取中位数96
下面就不再测了,96可能就是最后成绩,至此我们的优化效率提高了219%