Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何判断算法是否有可优化空间?

如何判断算法是否有可优化空间?

作者头像
BBuf
发布于 2020-10-30 03:18:37
发布于 2020-10-30 03:18:37
1.4K00
代码可运行
举报
文章被收录于专栏:GiantPandaCVGiantPandaCV
运行总次数:0
代码可运行

❝【GiantPandaCV导语】计算Armv7a架构理论gflops以及自己写的某个算法的gflops的方法,另外提供了一个脚本可以显示native版矩阵乘法各个尺寸对应的gflops。 ❞

1. 前言

之前一直在写一些算法怎么优化,包括算法逻辑甚至是更加底层一些的文章,但是测试工作都做得比较随意,也就是粗略的比较时间。最近准备学习一下矩阵乘法的优化,觉得这种比较方式实际上是看不出太多信息的,比如不知道当前版本的算法在某块指定硬件上是否还存在优化空间。因此,这篇文章尝试向大家介绍另外一个算法加速的评判标准,即算法的浮点峰值(gflops)。

❝gflops代表计算量除以耗时获得的值。 ❞

之前高叔叔发了一篇文章教会我们如何计算硬件的浮点峰值(https://zhuanlan.zhihu.com/p/28226956),高叔叔的开源代码是针对x86架构的。然后,我针对移动端(ArmV7-a架构)模仿了一下,在测出硬件的浮点峰值之后,手写了一个Native版的矩阵乘法并计算这个算法的gflops,以判断当前版本的算法离达到硬件浮点峰值还有多少优化空间。

2. Cortex-A17 硬件浮点峰值计算

详细原理请查看:浮点峰值那些事 。这里再截取一下计算浮点峰值的操作方法部分:

来自https://zhuanlan.zhihu.com/p/28226956

所以参考这一方法,即可在移动端测出浮点峰值,首先写出测试的核心代码实现,注意gflops的计算方法就是用计算量除以程序耗时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <time.h>
#include <stdio.h>

#define LOOP (1e9)
#define OP_FLOATS (80)

void TEST(int);

static double get_time(struct timespec *start,
                       struct timespec *end) {
    return end->tv_sec - start->tv_sec + (end->tv_nsec - start->tv_nsec) * 1e-9;
}



int main() {
    struct timespec start, end;
    double time_used = 0.0;

    clock_gettime(CLOCK_MONOTONIC_RAW, &start);

    TEST(LOOP);

    clock_gettime(CLOCK_MONOTONIC_RAW, &end);

    time_used = get_time(&start, &end);
    printf("perf: %.6lf \r\n", LOOP * OP_FLOATS * 1.0 * 1e-9 / time_used);
}

注意这里的TEST是使用了纯汇编实现,即test.S文件,代码如下,为什么一次循环要发射10条vmla.f32指令,上面截取的计算方法部分讲的很清楚,这个地方也可以自己多试几组值获得更加精细的硬件FLOPs:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.text
.align 5

.global TEST

TEST:

.loop2:
    vmla.f32 q0, q0, q0
    vmla.f32 q1, q1, q1
    vmla.f32 q2, q2, q2
    vmla.f32 q3, q3, q3
    vmla.f32 q4, q4, q4
    vmla.f32 q5, q5, q5
    vmla.f32 q6, q6, q6
    vmla.f32 q7, q7, q7
    vmla.f32 q8, q8, q8
    vmla.f32 q9, q9, q9

    subs r0,r0,    #1
    bne .loop2

我在Cortex-A17上测试了单核的浮点峰值,结果如下:

测试结果

然后大概知道了硬件的浮点峰值,我们在优化自己的算法时就至少心中有数了。

3. 实现Native矩阵乘法,记录浮点峰值

接着,我们参考https://github.com/flame/how-to-optimize-gemm来实现一个Native版的矩阵乘法,即A矩阵的一行乘以B矩阵的一列获得C矩阵的一个元素(计算量为2 * M * N * K),并统计它的运算时间以计算gflops,另外为了发现矩阵乘法的gflops和矩阵尺寸的关系,我们将各个尺寸的矩阵乘法的gflops写到一个txt文件里面,后面我们使用Python的matplotlib库把这些数据画到一张图上显示出来。首先实现不同尺寸的矩阵乘法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define A( i, j ) a[ (i)*lda + (j) ]
#define B( i, j ) b[ (i)*ldb + (j) ]
#define C( i, j ) c[ (i)*ldb + (j) ]
// gemm C = A * B + C
void MatrixMultiply(int m, int n, int k, float *a, int lda, float *b, int ldb, float *c, int ldc)
{
    for(int i = 0; i < m; i++){
        for (int j=0; j<n; j++ ){    
            for (int p=0; p<k; p++ ){      
                C(i, j) = C(i, j) + A(i, p) * B(p, j);
            }
        }
    }
}

测试和统计FLOPs部分的代码比较长,就贴一点核心部分吧,完整部分可以到github获取(https://github.com/BBuf/ArmNeonOptimization/tree/master/optimize_gemm):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// i代表当前矩阵的长宽,长宽都等于i
for(int i = 40; i <= 800; i += 40){
       m = i;
       n = i;
       k = i;
       gflops = 2.0 * m * n * k * 1.0e-09;
       lda = m;
       ldb = n;
       ldc = k;
       a = (float *)malloc(lda * k * sizeof(float));
       b = (float *)malloc(ldb * n * sizeof(float));
       c = (float *)malloc(ldc * n * sizeof(float));
       prec = (float *)malloc(ldc * n * sizeof(float));
       nowc = (float *)malloc(ldc * n * sizeof(float));
       // 随机填充矩阵
       random_matrix(m, k, a, lda);
       random_matrix(k, n, b, ldb);
       random_matrix(m, n, prec, ldc);

       memset(prec, 0, ldc * n * sizeof(float));

       copy_matrix(m, n, prec, ldc, nowc, ldc);

       // 以nowc为基准,判断矩阵运行算结果是否正确
       MatrixMultiply(m, n, k, a, lda, b, ldb, nowc, ldc);

       // 循环20次,以最快的运行时间为结果
       for(int j=0; j < 20; j++){
           
           copy_matrix(m, n, prec, ldc, c, ldc);

           clock_gettime(CLOCK_MONOTONIC_RAW, &start);

           MatrixMultiply(m, n, k, a, lda, b, ldb, c, ldc);

           clock_gettime(CLOCK_MONOTONIC_RAW, &end);

           time_tmp = get_time(&start, &end);
           
           if(j == 0)
               time_best = time_tmp;
           else
               time_best = min(time_best, time_tmp);
       }

       diff = compare_matrices(m, n, c, ldc, nowc, ldc);

       if(diff > 0.5f || diff < -0.5f){
           exit(0);
       }

       printf("%d %le %le \n", i, gflops / time_best, diff);
       fflush(stdout);

       free(a);
       free(b);
       free(c);
       free(prec);
       free(nowc);
}
printf("\n");
fflush(stdout);

「在编译之后运行时只需要新增一个重定向命令,即可获得记录了矩阵大小和GFlops的txt文件,例:./unit_test >> now.txt, 注意now.txt需要自己先创建,并保证它有可写的权限。」

接下来我们使用下面的脚本将now.txt用图片的方式显示出来,并将图片保存到本地:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import matplotlib.pyplot as plt
import numpy as np

def solve(filename):
    f = open(filename)
    sizes = [40]
    times = [0.0]
    title = 'origin'
    while True:
        line = f.readline()
        if line:
            slices = line.split(" ")
            if len(slices) <= 2:
                break;
            size = int(slices[0])
            time = float(slices[1])
            sizes.append(size)
            times.append(time)
    return title, sizes, times

if __name__ == '__main__':
    plt.xlabel('size')
    plt.ylabel('gflops')
    t, x, y = solve('now.txt')
    plt.plot(x, y, label=t)
    plt.legend()
    plt.savefig('origin.png')
    plt.show()

我们来看一下结果:

Native版矩阵乘法的gflops

从这张图可以看到,在矩阵长宽取100的时候可以达到最高的gflops大概是0.25gflops,相对硬件的理论浮点峰值只有2-3%,所以此算法的优化空间还是非常巨大的,接下来我们就可以使用如减少乘法次数,内存对齐,分块等策略去改进这个算法获得更好的gflops。这样,我们在算法优化的过程中就可以更加直观的看到算法的性能。

4. 小结

这篇文章只是矩阵优化部分的开篇,主要是受到高叔叔的文章启发给对移动端或者PC端感兴趣的同学提供一个gflops的计算实例,并提供一个将gflops更加直观显示的脚本工具,希望对大家有用。

5. 参考

  • https://zhuanlan.zhihu.com/p/65436463
  • https://zhuanlan.zhihu.com/p/28226956
  • https://github.com/flame/how-to-optimize-gemm
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GiantPandaCV 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基于how-to-optimize-gemm初探矩阵乘法优化
这次,我们来聊一个轻松一点的话题,那就是给你一个矩阵A和一个矩阵B,使用矩阵乘法获得目标矩阵C,相信大家都不难写出下面的代码:
BBuf
2020/11/09
1.4K0
基于how-to-optimize-gemm初探矩阵乘法优化
cuBLAS矩阵乘法性能分析(附代码示例)
矩阵乘法是神经网络中最基础、最重要的一个运算。在用CUDA实现矩阵乘法时,不需要我们手动写,cuBLAS库提供了现成的矩阵乘法算子,例如cublasGemmEx和cublasLtMatmul。其中后者是轻量级版本,API调用更灵活。例如对于整数乘法,cublasLtMatmul支持int8的输入输出,而cublasGemmEx只支持int8输入,int32输出。
godweiyang
2021/09/08
2.7K0
道阻且长_再探矩阵乘法优化
【GiantPandaCV导语】本文记录了笔者最近的一些优化gemm的思路和实现,这些思路大多是公开的方案,例如来自how-to-optimize-gemm工程的一些优化手段,来自ncnn的一些优化手段等。最终,笔者目前实现的版本在armv7a上可以达到50%左右的硬件利用率(这个利用率的确还不高,笔者也是一步步学习和尝试,大佬轻喷),本文记录了这些思路以及核心实现方法。改好的行主序代码(x86+armv7a版本)可以直接访问https://github.com/BBuf/how-to-optimize-gemm获取。
BBuf
2020/12/08
6860
道阻且长_再探矩阵乘法优化
OpenBLAS 中矩阵运算函数学习
OpenBLAS 库实现成熟优化的矩阵与矩阵乘法的函数 cblas_sgemm 和矩阵与向量乘法函数 cblas_sgemv,二者使用方法基本相同,参数较多,所以对参数的使用做个记录。
泽霖
2023/11/29
8760
【TVM 三代优化巡礼】在X86上将普通的矩阵乘法算子提速90倍
本文主要梳理一下在21年接触到优化gemm的知识,做一个学习总结。行文的顺序大概为:
BBuf
2022/05/27
1.3K0
【TVM 三代优化巡礼】在X86上将普通的矩阵乘法算子提速90倍
【从零开始学深度学习编译器】二,TVM中的scheduler
在【从零开始学深度学习编译器】一,深度学习编译器及TVM 介绍我们已经知道TVM可以将各种深度学习训练框架的模型(计算图)转化为内部的Graph IR(Relay),然后通过TVM提供的指令生成模块将Graph IR翻译成特定硬件可执行的指令或者代码。总的来说的TVM的思想可以总结为表示和调度分离,所谓表示就是IR,调度就是scheduler。同时,在高性能计算方面TVM提供了多种调度源语(scheduler),包含了大多数常见的优化手段如算子融合,读写缓存,分块计算,并行计算等等,这些计算方法都可以通过scheduler进行实现。所以这一节,我们就一起来探索一下TVM中的scheduler。
BBuf
2021/04/16
2K0
文心一言 VS 讯飞星火 VS chatgpt (390)-- 算法导论25.1 4题
要证明由 EXTEND-SHORTEST-PATHS 所定义的矩阵乘法是相关的,我们首先需要理解 EXTEND-SHORTEST-PATHS 算法的基本工作原理。EXTEND-SHORTEST-PATHS 通常用于计算两个加权有向图的乘积,其中图的权重表示从一个顶点到另一个顶点的最短路径长度。
福大大架构师每日一题
2024/11/13
1140
文心一言 VS 讯飞星火 VS chatgpt (390)-- 算法导论25.1 4题
详解卷积中的Winograd加速算法
做过ACM/OI的朋友大家应该对FFT并不陌生,我们知道对于两个序列的乘法通过FFT可以从原始O(n^2)复杂度变成O(nlogn),所以我们就会想着FFT这个算法是否可以应用到我们计算卷积中来呢?当然是可以的,但是FFT的计算有个问题哦,会引入复数。而移动端是不好处理复数的,对于小卷积核可能减少的计算量和复数运算带来的降速效果是不好说谁会主导的。所以在这种情况下,针对卷积的WinoGrad算法出现了,它不仅可以类似FFT一样降低计算量,它还不会引入复数,使得卷积的运算加速成为了可能。因此,本文尝试从工程实现的角度来看一下WinoGrad,希望对从事算法加速的小伙伴有一些帮助。
BBuf
2020/10/10
5.2K0
矩阵相乘在GPU上的终极优化:深度解析Maxas汇编器工作原理
在从事深度学习框架的实现工作时,了解到 Nervana 有一个称为 Maxas 的汇编代码生成器项目,可以生成性能超过 nVidia 官方版本的矩阵相乘的 GPU 机器码,由此对其工作原理产生兴趣。
机器之心
2020/05/19
9930
矩阵相乘在GPU上的终极优化:深度解析Maxas汇编器工作原理
详解Im2Col+Pack+Sgemm策略更好的优化卷积运算
❝[GiantPandaCV导语] 「这篇文章是基于NCNN的Sgemm卷积为大家介绍Im2Col+Pack+Sgemm的原理以及算法实现,希望对算法优化感兴趣或者做深度学习模型部署的读者带来帮助」。 ❞
BBuf
2020/09/21
3.2K0
详解Im2Col+Pack+Sgemm策略更好的优化卷积运算
性能比拼!超详细的Tengine GEMM矩阵乘法汇编教程
Tengine 是OPEN AI LAB 针对前端智能设备开发的软件开发包,核心部分是一个轻量级,模块化,高性能的AI 推断引擎,并支持用DLA、GPU、xPU作为硬件加速计算资源异构加速。
CV君
2019/12/27
2.4K0
【算法竞赛】XDU-ACM校赛2023题解 - DEFGIJ
因为我做期望题很少,赛时直接跳了,后面推推感觉还行,大概设计好,有终结状态的状态信息一个就行
Livinfly
2023/05/19
3580
【算法竞赛】XDU-ACM校赛2023题解 - DEFGIJ
未来FPGA能击败GPU么?这是英特尔的研究成果
问耕 编译整理 量子位·QbitAI 报道 在最近的FPGA国际研讨会(ISFPGA)上,英特尔加速器架构实验室(AAL)的Eriko Nurvitadhi博士,发表题为《Can FPGAs beat GPUs in Accelerating Next-Generation Deep Neural Networks》的报告,分享了英特尔的最新研究。 这一研究,主要评估在DNN(深度神经网络)算法领域,两代英特尔FPGA(Intel Arria10和Intel Stratix 10),与NVIDIA TITA
量子位
2018/03/22
8510
未来FPGA能击败GPU么?这是英特尔的研究成果
【AlexeyAB DarkNet框架解析】五,卷积层的前向传播解析
今天来介绍一下DarkNet中卷积层的前向传播和反向传播的实现,卷积层是卷积神经网络中的核心组件,了解它的底层代码实现对我们理解卷积神经网络以及优化卷积神经网络都有一些帮助。
BBuf
2020/02/21
1.3K0
【AlexeyAB DarkNet框架解析】五,卷积层的前向传播解析
cuDNN 5对RNN模型的性能优化
原文:Optimizing Recurrent Neural Networks in cuDNN 5 作者:Jeremy Appleyard 翻译:赵屹华 审校:刘翔宇 责编:周建丁(zhoujd@csdn.net) 在GTC2016大会上,NVIDIA发布了最新版本的深度学习开发包,其中包括了cuDNN 5。第五代cuDNN引入了新的特性,提升了性能,并且支持最新一代的NVIDIA Tesla P100 GPU。cuDNN的新特性包括: 使用Winograd卷积算法,计算前向、后向卷积速度更快; 支
用户1737318
2018/06/06
2.4K0
手撕 | 深度神经网络卷积层计算加速与优化
最后一页没画,但是基本上就是Filter Matrix乘以Feature Matrix的转置,得到输出矩阵Cout x (H x W),就可以解释为输出的三维Blob(Cout x H x W)。
OpenCV学堂
2019/09/25
2.4K0
手撕 | 深度神经网络卷积层计算加速与优化
【BBuf的cuda学习笔记十】Megatron-LM的gradient_accumulation_fusion优化
这篇文章来解析一下Megaton-LM涉及到的一个优化gradient_accumulation_fusion。这里fusion的意思是在gemm接口中会将当前的结果累加到先前计算的梯度上,所有这些都在一个操作中完成,可以避免多次访问global memory提升算子的带宽。下面解析一下这个优化的调度逻辑和cuda实现。
BBuf
2023/08/25
2K0
【BBuf的cuda学习笔记十】Megatron-LM的gradient_accumulation_fusion优化
DeepSeek开源周 Day03:从DeepGEMM看大模型算力提速的矩阵乘法
今天是DeepSeek开源周的第三天,继FlashMLA和DeepEP之后,DeepSeek开源了DeepGEMM库。作为一个专注于FP8精度通用矩阵乘法的高性能库,DeepGEMM在提供极致性能的同时保持了令人惊讶的代码简洁性。
致Great
2025/02/27
3420
DeepSeek开源周 Day03:从DeepGEMM看大模型算力提速的矩阵乘法
AI芯片:高性能卷积计算中的数据复用
深度学习的发展过程中,较高的计算量是制约其应用的因素之一。卷积神经网络中,主要计算为三维的卷积计算(后简称为卷积),现有的主流处理器难以高性能,高效能的完成卷积计算。相比一般的通用计算,卷积计算中存在的大量数据复用以及计算的规则性,在硬件的微架构(后简称为架构)设计和计算优化上有很大的优化空间,由此诞生了众多针对深度学习加速的AI芯片。卷积计算过程可以表示如下
sea-wind
2019/09/11
2.5K0
AI芯片:高性能卷积计算中的数据复用
可以让深度学习编译器来指导算子优化吗
之前在阅读Ansor论文的时候(https://zhuanlan.zhihu.com/p/390783734)我就在想这样一个问题,既然Ansor是在人为指定的推导规则下启发式的生成高性能的Scheduler模板。那么这个算子生成的Scheduler模板是否可以反过来指导我们写程序呢?嗯,然后我就开启了这个实验,但最近因为工作的事情delay得厉害,终于在这个周末抽出时间来更新这个实验结果并且记录了这篇文章。由于笔者只对GEMM的优化熟悉,这里就以优化X86的GEMM为例子来探索。希望这篇文章能为你带来启发,文章所有的实验代码都放到了https://github.com/BBuf/tvm_learn ,感兴趣的可以点个star一起学习(学习TVM的4个月里,这个工程已经收到了快100star了,我很感激)。
BBuf
2021/09/14
9730
可以让深度学习编译器来指导算子优化吗
推荐阅读
相关推荐
基于how-to-optimize-gemm初探矩阵乘法优化
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档