C/C++中榨干硬件性能的N种并行姿势初探

简介: # 1. 前言 关于并行计算介绍参见 https://computing.llnl.gov/tutorials/parallel_comp/ 下面主要就部分单进程中常见的几种并行优化技术和相应的框架做一些简单的整理和分析对比,并且主要偏重于端,不涉及多节点多进程! # 2. 并行计算结构分类 目前常见的是分类方法主要是Flynn提出的经典分类法:根据指令流(单指令或多指令

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由于本身启动开销比较大,只有在运算复杂的情况下才会显示出性能优势,并且是运算越复杂,性能提升会越明显!

上面的测试目前并不涉及共享内存访问等也可能影响到性能的方面,实际使用中需要根据不同的场景使用不同的模型来使得性能获得最优....

目录
相关文章
|
2月前
|
存储 分布式计算 安全
我的C++奇迹之旅:值和引用的本质效率与性能比较2
我的C++奇迹之旅:值和引用的本质效率与性能比较
|
2月前
|
编译器 C++
我的C++奇迹之旅:值和引用的本质效率与性能比较1
我的C++奇迹之旅:值和引用的本质效率与性能比较
|
2月前
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
310 0
|
2月前
|
Java Linux C++
性能工具之 C/C++ 分析工具 valgrind
【5月更文挑战第26天】性能工具之 C/C++ 分析工具 valgrind
27 2
性能工具之 C/C++ 分析工具 valgrind
|
2月前
|
缓存 编译器 数据处理
【C/C++ 性能优化】循环展开在C++中的艺术:提升性能的策略与实践
【C/C++ 性能优化】循环展开在C++中的艺术:提升性能的策略与实践
173 0
|
2月前
|
存储 算法 编译器
C++性能调优:从代码层面提升程序效率
本文探讨了C++程序性能调优的关键点:选择合适的数据结构和算法,例如用哈希表(如`std::unordered_map`)替换低效的数组或链表;减少不必要的内存分配和释放,利用智能指针和容器如`std::vector`自动管理内存;优化循环和条件语句,例如在循环外存储数组大小;利用编译器优化如`-O2`或`-O3`;以及使用性能分析工具如`gprof`、`callgrind`和`perf`识别并解决性能瓶颈。通过这些方法,可以有效提升C++程序的运行效率。
|
2月前
|
存储 算法 编译器
【C++ 函数 基础教程 第四篇】深入C++函数返回值:理解并优化其性能
【C++ 函数 基础教程 第四篇】深入C++函数返回值:理解并优化其性能
237 1
|
2月前
|
缓存 算法 编译器
【C/C++ 泡沫精选面试题01】提高c++性能,你用过哪些方式去提升?
【C/C++ 泡沫精选面试题01】提高c++性能,你用过哪些方式去提升?
62 1
|
2月前
|
算法 Java 测试技术
【深入探究 C++ 日志库性能比较】glog、log4cplus 和 spdlog 的日志输出性能分析
【深入探究 C++ 日志库性能比较】glog、log4cplus 和 spdlog 的日志输出性能分析
564 0
|
2月前
|
存储 程序员 C++
C++容器初始化方式详解:优缺点、性能与应用场景
C++容器初始化方式详解:优缺点、性能与应用场景
42 0