Darknet项目性能优化

本文介绍了如何通过编辑Darknet源码,优化gemm_nn函数以提升矩阵乘法的性能。通过调整矩阵布局、并行计算和循环块划分,作者成功将计算时间从11.98秒降低到3秒,实现了约219%的效率提升。
摘要由CSDN通过智能技术生成

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函数的性能,可以考虑以下几个方面:

  1. 矩阵布局优化:优化内存访问模式以提高缓存命中率。可以使用行主序存储矩阵数据,这样可以利用局部性原理,使得连续的内存访问更加高效。

  2. 并行计算:可以使用多线程或者并行指令集(如SIMD指令)来加速计算。例如,使用OpenMP或CUDA等并行编程技术,在循环层级进行并行化,提高计算效率。

  3. 循环展开:通过展开内层循环,减少循环迭代次数,从而减少循环控制的开销,并提高指令级并行性。

  4. 数据复用:通过重用计算中的临时变量,减少内存读写操作,提高数据访问效率。

  5. 运算指令选择:使用适当的运算指令,如乘法指令、加法指令等,以提高运算速度。可以根据平台的特性选择适合的优化指令。

  6. 内存对齐:确保输入和输出数据在内存中的对齐,以使访问数据更加高效。

  7. 编译器优化:使用合适的编译器选项,如优化级别、循环展开选项等,以帮助编译器进行更好的代码优化。

那么算法本身诞生这么多年了,算法层面不可能让你捡漏,所以基于硬件优化是唯一选择,接下来我们使用循环分块提高缓存命中率的方式优化。

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%

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值