多线程编程1

多线程编程库

  • pthread, POSIX标准规定了UNIX及Linux实现的多线程接口pthread。pthread在所有linux下可用,业界广泛使用。
  • win32 thread,Windows系统内置的线程API
  • OpenMP,一个开放的,基于编译制导语句的API,目前各大操作系统上均可用,由于与操作系统平台无关,提供了比较好的可移植性,因此应当优先使用。
  • C/C++标准线程
  • OpenCL和CUDA,它们是基于GPU(OpenCL正在用于FPGA上编程)的并行计算架构、语言和API,由于在某些问题上比CPU更快的解决问题,目前广泛使用,适用范围正在逐渐扩大。
  • OpenACC,基于加速器的编译制导标准,通常用于GPU、FPGA等的并行编程

多核上多线程并行需要注意的问题

  • 线程过多: 频繁上下文切换,减低性能,内存污染
  • 数据竞争
  • 死锁
  • 饿死
  • 伪共享

缓存一致性策略 MESI

  • M (modified),更改,表示缓存中数据已更改,在未来的某个时刻将写入内存
  • E (exclusive),排除,表示缓存的数据只被当前的核心所缓存
  • S (shared), 共享,表示缓存的数据还被其他核心缓存
  • I (invaid),无效,表示缓存中的数据已经失效,其他核心更改了数据

NUMA技术

Intel处理器,多路处理器之间通过QPI总线通信;AMD处理器通过HT总线通信
QPI和HT的带宽比内存带宽小,延迟大于访问内存延迟,因此这是NUMA存在的原因

算法性能

  • 读写数据内存墙
  • 不同算法不同的限制因素,有些算法的大部分性能限制在读取网络数据上,有些算法限制在TLB上
    TLB Translation Lookaside Buffer 传输后备缓冲器,一个内存管理单元,改进虚拟内存到物理地址内存转换速度的缓存,如果没有TLB,每次取数据都需要访问两次内存,即查页表获得物理地址和取数据
  • 不同处理器执行不同指令的速度差异,生产商将更多的晶体管用于更常见的命令

计时方法

C标准库的time、clock系列函数,CUDA事件,Linux下的各塔timeofday,Windows下的GetTickCountdeng
通常使用clock和getTimOfSecond函数已够

程序性能分析实用工具

串行程序的剖分:Linux下的工具 perf、gprof、vargrind,其他比较著名且简单的有VS2010自带的剖分器和Intel Vtune工具
并行程序的剖分:TAU和Vampire,Vampire不仅支持MPI还支持OpenMP和pthread

  1. gprof
    GNU编译器工具包提供的性能分析工具,能够给出调用关系,调用次数,执行时间等信息,大多数linux发行版默认安装了gprof。gprof通常和gcc/g++配合使用,添加选项 -pg -g (pg表示产生的程序可以用gprof分析),gcc/g++会自动在程序中插入一些代码以保存函数执行时间、执行次数、调用关系等。
  1. nvprof
    NVIDIA 开发的、用于分析运行在其GPU上的CUDA程序性能的工具。目前只支持运行在NVIDIA GPU 上的内核的分析

串行代码的性能优化

  • 系统级别,找出程序性能控制因素,以做针对性的优化
  • 应用级别,代码编写前确定应用级别的配置,比较简单,实现后性能比较稳定
  • 算法级别,数据组织方式,算法对性能影响作用非常大
  • 函数级别,减少函数调用开销,或者减少函数调用带来的编译器性能优化阻碍
  • 循环级别,发掘循环并行性,减少循环内冗余计算
  • 语句级别,尽量使用语句条数少的语句
  • 指令级别,优先使用吞吐量大延迟小的指令

系统级别

  1. 如果应用通过网络互连,交换数据或指令,那么网速,利用率,网络负载均衡
  2. 处理器利用率:top命令输入1后
  • 第二行:Tasks表示系统目前总共有多少进程,多少进程正在执行,多少进程正在休眠,多少进行结束,多少僵尸进程。
  • 中间格列:表示各个核心上的占用情况,第一列逻辑核心编号,第二列用户空间对核心的占用率,第三列系统空间对核心的占用率
  • Mem行:表示系统内存信息
  1. 存储器带宽利用率:
  • 提高存储器访问的局部性以增加缓存利用,二维数据C语言优先访问行,Fortran语言优先访问列
  • 通识读写多个数据
  • 减少读写依赖
  • 将数据保存到临时变量以减少存储器的读写,临时变量通常占用空间小,硬件能够将它们缓存到一级缓存,或编译器能够将他们分配到存储器中,避免访问更慢的存储器
  • 减少io操作

应用级别

  1. 编译器选项
    如GCC有O0(编译器对程序不做优化),O1、O2、O3(为了性能做极致优化)优化选项,还有指定处理器架构,是否使用循环展开,是否使用SSE等优化选项 使用GCC时建议使用如下编译选项
-O3 -ffast-math -funroll-all-loops -mavx -mtune=native
# 其中,fast-math表示对超越函数使用更快但精度更低一些的版本;
# unroll-all-loops表示使用循环展开;
# avx表示使用avx指令集向量化;
# tune=native表示为当前编译的处理器做优化。
  1. 调用高性能库
  2. 去掉全局变量,应当是绝对禁止的,因为多个控制流需要协调对全局变量的更改,如何保证一致,是并行编程的难点,解决这个难点的最好策略是回避他
  3. 受限的指针
  4. 条件编译

算法级别

  1. 索引顺序
# 优化前的代码
for(int i=0; i<N; i++){
    for(int j=0; j<M; j++){
        r[j] += a[j][i];
    }
}

# 优化后的代码
for(int i=0; i<N; i++)
{
    float ret = 0.0f;
    for(int j=0; j<M ;j++)
    {
        ret += a[i][j];
    }
    r[i] = ret;
}   

优化前,内层循环上,相邻循环体内访问a的地址相隔N个单元,在N比较大的情况下,可能会存在满不命中和冲突不命中的情况,访问a的局部性很差。理想情况下,编译器自己能够做这种形式的代码转换,但是如果循环代码很复杂,再优秀的编译器可能也无能为力。
2. 缓存分块

# 缓存分块代码示例: 矩阵乘法
for(i=0;i<N;i+=NB){
    for(j=0;j<M;j+=NB){
        for(k=0;k<K;k+=NB){
            for(i0=i;i0<i+NB;i0++){
                for(j0=j;j0<j+NB;j0++){
                    for(k0=k;k0<K;k0++){
                        C[i0][j0]+=A[i0][k0]+B[i0][k0]
                    }
                }
            }
        }
    }
}

如果数据超过了缓存大小,容易出现不同层次缓存不命中的情况
3. 软件预取
4. 查表法
提前把数据组织成表格,预先计算

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值