我们平时在做业务的时候很少会有性能上的瓶颈,偶尔产生的UI层的卡顿也是我们对程序结构设计的不合理产生的。但是性能优化却是一个好的程序员所应具备的,而且在某些特殊的场景下,我们还要有能力去解决很多极限的性能问题,这里就来聊聊高性能计算方面的问题。
首先我们将这类问题分为两种:
- 减少计算量
- 加快单位时间的计算量
减少计算量
对于减少计算量来说,大家都是最熟悉的,也是平时碰到的最多的方法。
算法优化
最常见的要属算法优化,比如查找算法,用二分法代替顺序查找,排序用快速排序代替冒泡排序。
另一个比较典型的例子就是高斯模糊算法,将一次二维矩阵的乘法转化为两次一维矩阵的乘法,虽然看似改变很小,但是在整个图片的计算量上出入非常的大。以3*3
大小的矩阵来看,一次二维矩阵乘法需要9
次浮点乘法,而两次一维矩阵仅需要2*3
次浮点乘法,如果是6*6
的矩阵,这个比例将会达到36/12
。
数据结构优化
在我们需要更高性能的时候,我们不太可能一个方案能够满足所有的情况,往往需要特定的场景使用特定的方案。
在随机读取远大于随机写入的时候,数组的确是一个好方案,但是如果随机写入远大于随机读取的时候,链表的性能就会优于数组。如果读取和写入都比较频繁,那么树的结构可能会是你的首选,根据所需不同可以采用平衡二叉树,B/B+树,跳跃链表等。
如果需要大量写入磁盘,顺序的读取,比如日志系统这种,那么Google的LevelDB也是一个非常好的选择。
总之,根据需要来选择适合的数据结构也能大量的提升性能。
空间换取时间
要知道,很多的计算是重复的,而这些重复的计算可能会消耗我们大量的时间,那么我们在空间充足的情况下,何不缓存这一部分的数据的?
其实很多的第三方库都使用了这种思想的,索引
也可以认为是这种思想。这里举个简单的例子。
在图片处理的过程中,卷积的计算量是非常大的,而乘法占了很大一部分比例。而根据我的了解,ARM在处理乘法的时候,可能需要6个时钟周期,而加法只需要一个,如果能将乘法的结果缓存下来,通过加法访问是不是一种可行的方案呢?一个颜色的色值只有0~255的区间,是一个可以穷举的范围,所以缓存这部分结果,通过偏移量(也就是加法)来访问结果,是否也可以减少计算时间呢。
这种方法一般都会有一个阈值,超过这个阈值之后才会有明显的性能差距,所以在使用之前需要评估好自己的场景是否会处于优化的阈值内。
低等语言实现
很多高级语言在实现的时候,会附加一部分高级语言的特性,比如内存回收机制等,越是底层的语言我们越是能够控制其计算量。
极端的例子就是采用汇编方案,这样能够最为极限的控制其性能。当然这样的问题是兼容性问题,需要为多个平台分别写汇编代码。其中OpenCV中的部分计算过程就是采用了汇编语言实现的。
这种方案对于其他高级语言(JAVA,JS等),采用C来实现其底层,或许是个好的尝试。但是使用汇编这种方法虽然有效,但是不太建议,毕竟在晦涩程度和兼容性上考虑,带来的性价比并不高,只有在极少数的特定情况下,才会考虑。
加快单位时间的计算量
除了从计算量上来优化,我们还可以通过一些其他手段,包括更优秀的硬件来帮助我们。
并行计算
目前的大部分CPU都不是单核的了,包括很多移动设备上的。但是如此高的性能我们往往不能很充分的利用。
在iOS中就有一个适合并发计算的接口dispatch_apply
。采用多线程可以最大限度的开发出CPU的潜能。
CPU层面上的浮点计算
很多场景下,都是浮点乘法运算消耗了大量的时间,特别是对于ARM系列,但是新型的ARM特别设计了关于浮点及向量的优化(VFP或者称NEON)。
在iOS中,vImage就是利用了这一特性进行了优化,如果有少量的图片运算可以使用vImage来加快我们的速度。
ARM流水线优化
ARM系列的CPU一般都有采用流水线的架构,因为每一条指令的执行都需要经过取址-译码-执行
这几个步骤,采用流水线架构可以增加执行效率。
但是这也有一个反例,就是在B
相关跳转指令的时候,需要清空流水线,重新加载,这也会带来一定的性能损耗。
也就是说:
int i = 0; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
i++; x += i;
比
for (int i = 0; i <= 10; i++) x += i;
效率要高很多。当然这里有点吹毛求疵了,一般情况下我们还是不需要考虑这部分内容的。
GPU
其实目前来说,影响最大的莫过于图像的处理,普通的业务慢一点和快一点其实很难分辨出来,也没有必要去如此极限的优化,而图像的计算量之庞大,很容易就可以感觉出来,同时图像处理速度还制约着视频的帧率问题,所以问题的严重程度要高很多。
那么说到图像处理,就不得不涉及到GPU了。目前移动端基本都支持OpenGL2.0或OpenGL3.0,所以如果要跨平台使用可以考虑OpenGL来优化。但是如果只考虑iOS平台,让我们来看看iOS平台有哪些特定的利用GPU进行优化的方案。
GPUImage
这是一个利用OpenGL的开源库,是一个跨平台的第三方库,里面封装了很多滤镜,同时也支持图片、视频的处理,对于自定义和扩展也比较方便,是一款非常好用的开源库。
CIContext
Core Image是苹果官方提供的一款图像处理库,里面包含了众多的滤镜。其中CIContext可以指定为glContext,就是GPU环境了。一般来说,我们平时开发使用,CIImage已经足够了。
CImage的扩展也非常方便,有一种类似于openGL的GLSL的语言,KLSL。有兴趣的人可以自己去研究研究。
Metal
Metal是苹果比较新的一个库,专门为了替代OpenGL而做的,降低了OpenGL的学习成本。
据官方称,加入了众多的优化。其中一个比较明显的就是,将shader的编译过程放到了编译期,而不是运行期,也就是说,比起OpenGL,少了一步glCompileShader
。
同时Metal也和iOS的特性结合的比较好,使用起来也比OpenGL简单很多。当然这些都是iOS平台的特性,不支持跨平台。
OpenCL
这个是对应于mac系统的,其他系统上也有实现,目前还不支持移动端。
多缓冲FrameBuffer的GPU
对于视频这类连续的计算,以上的方案已经非常的优秀了,但是我们还是没有榨干机器的性能。:D
1.png一张图片的处理流程可以表示为上图,CPU需要将数据参数准备好,然后拷贝到GPU内存空间,然后等待GPU执行。GPU执行完之后,需要等待CPU准备好下一张图片的数据并拷贝到GPU空间,在这之间是留了很多的空白时间的。
我们知道OpenGL并不是线程安全的,也就是说GPU空间是可以多线程同时访问的,那么我们可以通过多个缓冲区来解决上述空白时间的问题。
2.png上图就是我们想要达到的效果,而下图是我们采用3个缓冲区,实际上的效果。
3.png最后
以上是对高性能计算几种方式的一个简单概括,具体的情况需要根据自己的实际情况来选择。
网友评论