Intel多线程技术的使用与优化——《多核多线程技术》

1、帮助测试的软件

1. Intel VTune性能分析器

       Intel VTune性能分析器能够帮助定位程序中与性能有关的问题。其在Windows下支持图形化界面,可支持命令行输入。主要功能有:算法

        取样功能能够帮助开发者定位程序中最消耗时间的函数和模块;编程

         曲线图可肯定程序运行时函数调用顺序和显示关键路径;安全

         计数器监控器肯定是否会由于可用内存减小或文件输入/输出而致使程序速度变慢;数据结构

         调优助手自动推荐代码改进方法。多线程

2. MKL数学核心函数库

         该库利用Intel多核处理器,提供高度优化的函数,使程序得到更高性能并减小开发时间。并发

3. Thread Checker线程检查器

          快速查找和修复Windows和openMP软件中的线程bug(好比数据竞争),提示同线程错误相关的源代码位置。编程语言

 

2、与体系无关的优化方法

1.编译优化选项

           对于同一个函数来讲,调用编译器不一样的优化选项,产生的结果可能不一样。分布式

编译选项 函数1读写次数 函数2读写次数 读写所用时间
-g 8 read,2 write 6 read,2 write 0.21e-3/0.21e-3
-O2 1 read,1 write 1 read,0 write 0.3e-3/0.7e-5
-O3 2 read,1 write 2 read,1 write 0.27e-3/0.27e-3

       从中可看出,-O3不必定比-O2好,-O3采用inline技术,将函数直接嵌入main函数中,这样是否能就得看程序具体状况。函数

2.减小没必要要的内存存取

void combine(data_t *dest)
{
     int length = vec_length(v);
      for(int i=0;i<length;i++)
          *dest += data[i];
}

        在combine函数中,*dest函数属于传入参数,是经过堆栈被函数体内部引用的。所以不管是读写,都要进入系统内存,成本太高。其实只要用一个局部变量,存储中间计算结果,最后传给dest就好了。性能

3.函数调用的边际效应

int counter = 0;
int f(int x)
{
   return counter++;
}

int func1(x)
{
    return f(x)+f(x);
}

 

int counter = 0;
int f(int x)
{
   return counter++;
}

int func2(x)
{
    return 2*f(x);
}

 

       只要函数调用过程当中改变了某些全局变量的值,就称函数调用有边际效应。存在边际效应的函数因调用次数对程序有不一样影响。减小边际效应的方法就是 减小全局变量的使用。

4.数据结构

        C语言中16位int数据结构,其数据范围在-32768~32767。但实际使用时,用户可能只用int来定义一个枚举类型。

 

3、多线程编程常见问题

1.串行化

        虽然说多线程是并发的,但如遇到两个线程要读写同一个数据,为了数据安全,必然是一个线程先对数据进行操做另外一个线程在进行操做。这样在数据部分多线程其实是串行化运行。

        第一个解决方法是无锁算法,但过于复杂,并且容易出错。

        第二个解决方法是原子操做,本质上没有解决串行化问题,但可让串行化速度提高。惋惜目前厂商提供的原子操做有限。

        第三个解决方法是从设计层面来缩小串行化比例。使用任务分解模式、数据分解模式、数据共享模式等。

注:运行在双CPU或四CPU上的程序,因为锁竞争致使的加速系数降低现象不明显。但随着CPU核数增多,这个问题会变得很严重。多核编程与多任务编程是不同的。

2.线程过多

       将给定工做量划分给过多线程,会形成线程启动和终止的开销比实际运行的开销更大。并且,过多的会致使共享有限硬件资源的开销增大。

       建议使用线程池。

3.数据竞争与死锁

 

4、非阻塞算法

        非阻塞算法的本质是中止一个线程执行不会阻碍其余执行实体的运行,其特色在于:

         1)无阻塞:没有竞争,线程就能够持续执行。

         2)无锁:系统总体持续运行。

         3)无等待:每一个线程均可以执行,即便遇到竞争也是如此。只有极少算法(实现这一点)。

1.CAS操做

         一种基本的非阻塞算法叫作比较并交换(CAS),其包含三个数:内存位置(V)、预期原值(A)和新值(B)。若是内存位置的值与预期原值相同,那么将该位置的值更新为新值。

int test()
{
    int v;
    v = value.get();
    while(!value.compareAndSet(v, v+1));
    return v+1;
}

       上面是使用该方法的一个计数器例子:将一个变量更新为新值,但若是从我上次看到这个变量以后其余线程修改了它的值,那么更新失败,从新再来。

        在轻度和中度竞争状况下,非阻塞算法的性能会超过阻塞算法,由于CAS的大多数时间在第一次尝试时就成功,而竞争时开销也不涉及线程挂起,只多了几个循环迭代;在高度竞争状况下,基于锁的算法提供比非阻塞算法更好的吞吐率。

2.ABA问题

        在进行CAS操做时,主要看内存位置V的值A是否改变,但有一种状况:内存位置V的值A先改变了成了B,又从B变回了A。这种状况CAS算法便会产生问题。这类问题成为ABA问题。

        要解决该类问题,一般将标记与要进行CAS操做的每一个值相关联,并原子地更新值和标记。将ABA问题转化为ABA'。

3.cache乒乓现象

        若是将锁竞争分布到多个锁上,而且每一个锁可以保证在其线程完成操做以前没有线程可以访问它所保护的cache线,那么它比等价的非阻塞算法性能要好。

 

5、openMP编程

1.openMP简介

        openMP是一种面向共享内存以及分布式共享内存的多处理器多线程编程语言。其执行模型为fork-join形式:开始执行时,只有一个主线程运行,当遇到须要并行计算时,派生出fork来执行任务,任务结束后派生线程退出,只剩下主线程运行。

         该模型经过编译制导语句来执行。编译制导语句是指在编译器编译程序时,识别特定注释。例如在C/C++语句中,用#pragma  omp  parallel来标志后面一段为并行程序块。