当前位置: 首页 > 网络应用技术

手臂占用性能优化指南

时间:2023-03-07 02:27:42 网络应用技术

  作者:林金霍|查看Megengine建筑师

  在进行ARM Side Son Development时,我们不必关心的是性能。这篇文章主要是引入ARM操作员性能优化的常见想法,作为条目 - 级别的参考。该文章基于ARM Cortex A55上的Gaussianblur,并且总结文章末尾性能优化的想法优化。

  高斯布鲁尔是一个线性平滑滤波器。计算过程是:原始图片的每个点都加权并称量周围点,并获得相应位置的输出。重量矩阵是kernel.take kernel_size = 3作为一个例子,如图所示:

  示例代码如下:

  内核的值由高斯公式[^1]计算,并受到调节。例如,常用kernel_size = 3的内核IS:

  可以通过两个向量获得:$ {0.25、0.5、0.25}^t * {0.25、0.5、0.25} $。

  接下来,作者将介绍一些常见的优化想法:kernel_size = 3高斯布鲁尔作为一个例子:

  根据作者的经验,性能优化的主要好处来自算法级别的优化。这是从根本上减少计算量,因此第一步是考虑算法级别的优化。对于高斯过滤,它是一个单独的过滤器,这意味着

  分析:示例代码1的时间复杂性为$ O(H W KX KY)$,示例代码2的时间复杂性为$ O(H W *(KX + KY))$。

  简单地总结:从数学的角度来看,乐观是首先。这取决于您是否可以找到等效的算法或近似算法来降低算法的复杂性。相似的高斯布鲁尔的优化是'stack blur'[^2](多个BoxFilter来模拟高斯蓝蓝色);转换为频域上的计算。

  在优化了Point1算法设计之后,大大减少了计算量,但是在算法的实现过程中可能有很多重复计算,因此第二步被考虑以减少重复计算。示例代码2作为示例,以注意以下几点:

  简单地总结:在性能优化的过程中,您需要注意以前进行的计算,然后设计一个数据结构来缓存。在同一时间,请注意算法本身的某些特征(例如高斯核心是对称的),以查看它是否可以减少一些计算。

  前两个步骤基本上确保了操作是必要的,最少的,第三步需要考虑增加数据级并行性。

  数据级并行(DLP),主要方法是SIMD/SIMT,它被简单地理解为同时处理多个数据的指令。在ARM中,NEON指令集/SVE指令集被主要使用。以下示例代码仅计算以下示例代码2中行行行的前两行作为演示。

  摘要:获取一串代码,您可以考虑是否可以执行矢量化。

  此步骤是前三个步骤的补充,主要使用编译器尝试优化。

  简单地理解传输循环是for -loop的步骤 - 到 - 阶,因此每个迭代都可以处理更多的数据,为编译器提供更多的调度空间(例如,指令团聚,寄存器更名,寄存器REUSEIT,也同时减少分支机构判断的数量,从而提高性能。操作非常简单,示例代码如下:

  总结一点:需要根据实际情况对展开数量进行分析和测试。您也可以尝试不同的展开时间进行搜索和确认。

  前4个步骤已经完成了计算的优化,并且还需要优化访问,并且也被认为可以减少重复访问。

  观察样本代码3,分别为三个VLD1Q_F32负载

  可以发现,SRC [0] [J+3]重复了三次。因此,请考虑引入一组向量的头,身体,尾巴以减少重复访问。示例代码如下:

  这样,每个周期都需要3 VLD1Q,现在只需要一次。成本是更多的分配和VEXTQ。

  总结一下:当操作员遇到内存时,您可以考虑减少访问次数。例如,设计数据结构旨在减慢访问结果并减少重复访问。

  然后引起以下两个问题:

  Q1:我如何知道运算符是在计算中绑定还是访问的?

  可以借助根线模型来分析。Froofline模型主要是“在计算能力的峰值的设备上,带宽峰为b,运行计算量为c,并且带有D访问的程序可以到达性能峰E.有关详细信息,请参阅[^3]的论文。

  Q2:如何知道设备的峰值和带宽峰?

  设备的计算功率峰和带宽峰主要通过宏基准。您可以在GitHub上找到一些宏观基准,例如Stream,Lmbench等。

  优化计算和访问访问权限时,必须和最低限度确保计算和访问存款,然后考虑引入多线程。

  在示例代码2中,可以将整个高度拆除为几个部分,每个部分可以使用相同的代码执行,以便可以在并行处理中打开多个线程。

  前6个步骤属于C ++/仪器水平的粗调。此步骤是组装级别的优化。很好 - 态度。性能优化应遵循“第一粗调然后音调”的原则。当您不考虑C ++级别的其他优化点时,您可以考虑汇编优化。请简短地在此处介绍,但要做更多。主要是以下几点:

  必须注意的是:

  示例1:通过检查Cortex-A55 [^7]的优化指南,您可以获取以下信息:

  LDR指令(D-Form)负责指定地址负载64位的数据

  了解此信息,我们可以选择通过选择可以双重发言的说明组合来涵盖某些说明的目的。例如

  因此,LDR(D-Form)可用于替换LDR(Q-form)以使用FADD(Q-form)进行双重启动,从而涵盖了LDR指令的开销。::成功注意:128位D-Form Data :::::的数据D形式指令

  示例2:检查Cortex-A55 [^7]的优化指南,您可以知道FMLA,FMUL,FADD指令(Q-Form)的潜在是4个周期。吞吐量是1个周期。使用FMLA替换FADD+FMUL以减少一项指令。

  示例3:Cortex -A7是一个单一的启动,是订单执行的核心。然后,主要原因是考虑根据说明重新解雇指令的说明,并尽可能筋疲力尽。

  完成上述优化步骤后,如果性能不符合标准,则可以考虑以下优化。

  这里至少有两个方面。一方面,内存地址对齐。不同的硬件设备有一些对齐要求。例如,ARM AARCH64加载/商店指令需要访问地址和访问元素的大小(例如4个字节)。。对于相同的代码,不同的内存布局,访问的连续性不同。也将有一个自定义内存布局,在某些情况下可以实现良好的优化效果。

  C ++的写作可以更多地关注内部各方,报价,移动语义等。功能接口参数尽可能地使用简单的数据结构,这可以提高程序性能并减少不必要的费用。

  此方法允许编译器在编译链接时执行一些简单的操作。预先了解一些参数信息还可以帮助编译器优化。例如,您可以绘制一些判断为模板参数的标志。

  上面通过高斯布鲁尔(Gaussianblur)引入了一些可能的优化点,但这只是整个优化过程的一步。

  性能优化是一个连续的迭代过程,很难迈出一步。一般优化过程可以在以下图中表示:

  为了获得正确的优化反馈,有必要进行科学和严格的基准。作者认为,基准测试至少需要考虑以下因素:

  您还可以考虑使用基准工具,例如Google_benchmark。

  在进行性能优化之前,您通常需要进行配置文件以了解程序的热点(时间最多),并观察是否存在异常的开销(例如功能的超大开销)。

  您可以使用一些分析工具。硬件制造商通常提供自己的分析工具,例如在X86上使用Intel的VTUNE,使用Nvidia,nvidia,simple_perf在手臂映射上,Android在手臂上。

  您还可以手动添加计时函数以比较核心代码的速度和包装速度,以确定包装带来的开销是否合理。

  Github:Megengine Tianyuan(欢迎星星?

  Gitee:Megengine/Megengine

  官方网站:Megengine深度学习,简单开发

  欢迎加入MEGENGINE技术交换QQ组:1029741705

  [^1]:

  [^2]:堆栈Blur https://medium.com/mobile-app-development-publication/blurring-imgorithm-in-ndroid-cec81911cd5e

  [^3]:屋顶线模型https://people.eecs.berkeley.edu/~kubitron/cs252/handouts/fofine off.pdf

  [^4]:C ++嵌入式组装https://dmalcolm.fedorapeople.org/gcc/2015-08-31/ st-how-wo-wo-wo-wine-inline-inline-lanline-language-ingoge-in-code.html#outputoperands

  [^5]:编译器资源管理器https://godbolt.org/

  [^6]:ARM组装说明详细介绍https://developer.arm.com/documentation/ddi0487/ha/?lang=en

  [^7]:ARM Cortex-A55软件优化指南https://developer.arm.com/documentation/epm128372/0300/lang=en

  原始:https://juejin.cn/post/7099755343890087943