1. 前言
关于并行计算介绍参见
https://computing.llnl.gov/tutorials/parallel_comp/
下面主要就部分单进程中常见的几种并行优化技术和相应的框架做一些简单的整理和分析对比,并且主要偏重于端,不涉及多节点多进程!
2. 并行计算结构分类
目前常见的是分类方法主要是Flynn提出的经典分类法:根据指令流(单指令或多指令)和数据流(单数据流或多数据流)来分类,如下图:
几种常见的并行计算框架(技术)
1). 基于SIMD的并行优化技术
a. Neon技术优化
Neon技术是ARM公司在Arm-v7a及后续架构实现的一种SIMD(单指令多数据)结构的指令优化技术,通过ARM在汇编级别提供的neon指令,一条指令可以同时进行多条数据的并行处理,比如加,减等
详细介绍参考:
https://www.arm.com/zh/products/processors/technologies/neon.php
使用Neon指令优化可以根据需要在三个层次选择不同的优化措施:
a). 编译选项优化
目前gcc等已经在编译器级别集成了neon的优化,直接添加编译指令就可以使得应用在某些场合应用到neon的并行优化,一般添加编译开关如下:-mfloat-abi=softfp -mfpu=neon
b). 使用NEON Intrinsics(内联函数)优化
在使用了编译开关后性能还无法达到要求或对你的算法没有有效提升的时候,你可以直接调用编译器本身集成的neon 内联函数直接进行优化:如下面的一个RGB转灰度值的例子:
void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
uint8x8_t rfac = vdup_n_u8 (77);
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
n/=8;
for (i=0; i<n; i++)
{
uint16x8_t temp;
uint8x8x3_t rgb = vld3_u8 (src);
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], rfac);
temp = vmlal_u8 (temp,rgb.val[1], gfac);
temp = vmlal_u8 (temp,rgb.val[2], bfac);
result = vshrn_n_u16 (temp, 8);
vst1_u8 (dest, result);
src += 8*3;
dest += 8;
}
}
引自 http://hilbert-space.de/?p=22
这种方法本质上是编译器把汇编指令封装为c风格的函数,方便嵌入到c/c++中使用!
c). 直接调用汇编指令优化
详细参见 http://hilbert-space.de/?p=22
2). 基于多核的MIMD(多指令多数据)并行优化技术
目前常说的多核CPU处理器一般都属于MIMD架构,在这种架构上常用技术主要是多线程技术,目前常用的这类并行框架有 TBB ,CSTRIPES ,OPENMP,GCD (apple 专用),MS CONCURRENCYD (windows专用)等,下面所说的两种常用并行框架主要都是基于多核多线程的
a. tbb(Threading Building Blocks)
tbb 是intel开源的一个并行计算框架,主要介绍参见:
https://www.threadingbuildingblocks.org/
tbb的使用需要引入一个基于相关平台编译的独立库
用法很简单,例如
parallel_for(0, DATA_SIZE, [](int i) { res[i] = a[i] + b[i]; });
完成一个简单的for循环的并行计算
b. OpenMP
"OpenMP(Open Multi-Processing)是一套支持跨平台共享内存方式的多线程并发的编程API"
详细参见 https://zh.wikipedia.org/wiki/OpenMP
openmp的使用相当简单,只要在编译器中加入相应的编译开关即可使用-fopenmp
写法也很简洁,如
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < DATA_SIZE; i++) {
res[i] = a[i] + b[i];
}
}
完成一个简单的for循环的并行计算
3). GPU并行优化
GPU技术本身主要是在图形渲染中用来加速图形渲染的硬件架构,本质是使用了大量计算单元并行进行图形或计算处理,后面发现这个完全可以用来进行通用的并行计算, 于是许多基于此的并行框架和技术诞生了,目前主流的框架主要是cuda和opencl
a. OpenCL
"OpenCL(Open Computing Language,开放计算语言)是一个为异构平台编写程序的框架,此异构平台可由CPU、GPU、DSP、FPGA或其他类型的处理器与硬件加速器所组成"(https://zh.wikipedia.org/zh-cn/OpenCL)
OpenCL需要驱动支持才能使用,初始化工作比上述几种方法稍显复杂,此处不赘言...
3. 几种方法的简单测试对比
测试主要基于基本的加,减,乘,除,外加一个稍微复杂一点的混合运算,基本运算描述如下:
#define DATA_SIZE 10000000
float a[DATA_SIZE], b[DATA_SIZE], res[DATA_SIZE];
加
for (i = 0; i < DATA_SIZE; i++) {
res[i] = a[i] + b[i];
}
减
for (i = 0; i < DATA_SIZE; i++) {
res[i] = a[i] - b[i];
}
乘
for (i = 0; i < DATA_SIZE; i++) {
res[i] = a[i] * b[i];
}
除
for (i = 0; i < DATA_SIZE; i++) {
res[i] = a[i] / b[i];
}
混合运算
for (i = 0; i < DATA_SIZE; i++) {
res[i] = a[i] / b[i] / (b[i] / (a[i] + 1))/ (b[i] / (a[i] + 1));
}
测试数据1(单位:毫秒,取多次统计平均值)
note3(四核CPU+neon指令支持+OpenCL支持,配置属于稍高端)
$ cat /proc/cpuinfo
Processor : ARMv7 Processor rev 0 (v7l)
Features : swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt
Hardware : Qualcomm MSM 8974 (Flattened Device Tree)
|| case || cpu| opencl| openmp| neon| openmp+neon| tbb| tbb+neon|
||complex || 659| 319| 251| 0| 1| 188| 0|
|| div || 102| 232| 61| 89| 59| 49| 54|
|| mul || 89| 233| 59| 136| 61| 51| 57|
|| sub || 86| 230| 59| 142| 61| 49| 56|
|| add || 101| 227| 57| 133| 58| 48| 54|
测试数据2(单位:毫秒,取多次统计平均值)
m13(四核CPU+neon指令支持+不支持OpenCL,配置属于稍低端)
$ cat /proc/cpuinfo
Processor : ARMv8 Processor rev 4 (v8l)
Features : fp asimd crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt
Hardware : Amlogic
|| case || cpu| opencl| openmp| neon| openmp+neon| tbb| tbb+neon|
||complex || 1288| 0| 381| 0| 0| 365| 0|
|| div || 416| 0| 127| 224| 124| 134| 122|
|| mul || 253| 0| 107| 170| 125| 110| 122|
|| sub || 257| 0| 107| 162| 124| 110| 121|
|| add || 275| 0| 135| 170| 124| 222| 126|
其中neon使用了开源库 https://github.com/projectNe10/Ne10 提供的neon优化库做测试
4. 对比分析
从上面数据可以看出,
1). 整体上,tbb,openmp等多核多线程技术只要核足够,并行计算明显能够获得较大程度优化,简单计算基本上能够根据核数的多少获得相应倍数的提升
2). neon指令在cpu比较弱的机器中性能能够获得成倍的有效提升,但在CPU本身就很强劲的机器上,neon指令的优化效果并不明显,原因可能是在高端机器中CPU已经充分经过多数据流计算优化!
3). opencl由于本身启动开销比较大,只有在运算复杂的情况下才会显示出性能优势,并且是运算越复杂,性能提升会越明显!
上面的测试目前并不涉及共享内存访问等也可能影响到性能的方面,实际使用中需要根据不同的场景使用不同的模型来使得性能获得最优....