多核多线程 ppt总结.docx
《多核多线程 ppt总结.docx》由会员分享,可在线阅读,更多相关《多核多线程 ppt总结.docx(30页珍藏版)》请在冰点文库上搜索。
多核多线程ppt总结
1.CPU核心数据共享与同步的通信机制:
⏹总线共享Cache结构:
每个CPU内核拥有共享的二级或三级Cache,用于保存比较常用的数据,并通过连接核心的总线进行通信。
⏹基于片上互连的结构:
每个CPU核心具有独立的处理单元和Cache,各个CPU核心通过交叉开关或片上网络等方式连接在一起。
⏹给程序开发者带来的挑战
2.并行和并发
☐如果某个系统支持两个或多个动作(Action)同时存在,那么这个系统就是一个并发系统
☐如果某个系统支持两个或多个动作同时执行,那么这个系统就是一个并行系统
☐并发程序可同时拥有两个或多个线程。
如果程序能够并行执行,则一定是运行在多核处理器上,每个线程都将分配到一个独立的处理器核上。
☐“并行”概念是“并发”概念的一个子集
3.并行计算的目的、目标
并行计算技术的主要目的:
⏹加速求解问题的速度例如,给定某应用,在单处理器上,串行执行需要2周,这个速度对一般的应用而言,是无法忍受的。
于是,可以借助并行计算,使用100台处理器,加速50倍,将执行时间缩短为6.72个小时。
⏹提高求解问题的规模例如,在单处理器上,受内存资源2GB的限制,只能计算10万个网格,但是,当前数值模拟要求计算千万个网格。
于是,也可以借助并行计算,使用100个处理器,将问题求解规模线性地扩大100倍。
并行计算的主要目标:
在并行机上,解决具有重大挑战性计算任务的科学、工程及商业计算问题,满足不断增长的应用问题对速度和内存资源的需求。
4.并行计算的主要研究内容大致可分为四个方面:
⏹并行机的高性能特征抽取充分理解和抽取当前并行机体系结构的高性能特征,提出实用的并行计算模型和并行性能评价方法,指导并行算法的设计和并行程序的实现。
⏹并行算法设计与分析设计高效率的并行算法,将应用问题分解为可并行计算的多个子任务,并具体分析这些算法的可行性和效果。
⏹并行实现技术主要包含并行程序设计和并行性能优化。
并行应用这是并行计算研究的最终目的。
通过验证和确认并行程序的正确性和效率,进一步将程序发展为并行应用软件,应用于求解实际问题。
同时,结合实际应用出现的各种问题,不断地改进并行算法和并行程序。
5.并行程序执行时间
对各个进程,墙上时间可进一步分解为计算CPU时间、通信CPU时间、同步开销时间、同步导致的进程空闲时间
⏹计算CPU时间:
进程指令执行所花费的CPU时间,包括程序本身的指令执行占用的时间(用户时间)和系统指令花费的时间;
⏹通信CPU时间:
进程通信花费的CPU时间;
⏹同步开销时间:
进程同步花费的时间;
⏹进程空闲时间:
进程空闲时间是指并行程序执行过程中,进程所有空闲时间总和(如进程阻塞式等待其他进程的消息时。
此时CPU通常是空闲的,或者处于等待状态)
6.并行程序性能优化
最主要的是选择好的并行算法和通信模式
⏹减少通信量、提高通信粒度
提高通信粒度的有效方法就是减少通信次数,尽可能将可以一次传递的数据合并起来一起传递
⏹全局通信尽量利用高效集合通信算法
对于标准的集合通信,如广播、规约、数据散发与收集等,尽量调用MPI标准库函数
⏹挖掘算法的并行度,减少CPU空闲等待
具有数据相关性的计算过程会导致并行运行的部分进程空闲等待.在这种情况下,可以考虑改变算法来消除数据相关性
⏹负载平衡
必要时使用动态负载平衡技术,即根据各进程计算完成的情况动态地分配或调整各进程的计算任务。
动态调整负载时要考虑负载调整的开销及由于负载不平衡而引起的空闲等待对性能的影响,寻找最优负载调整方案。
⏹通信、计算的重叠
让通信和计算重叠进行,利用计算时间来屏蔽通信时间。
实现方法一般基于非阻塞通信,先发出非阻塞的消息接受或发送命令,然后处理与收发数据无关的计算任务,完成计算后再等待消息收发的完成。
⏹通过引入重复计算来减少通信,即以计算换通信
由于当前大部分并行计算机的计算速度远远大于通信速度,并且在一些情况下,当一个进程计算时,别的进程往往处于空闲等待状态,因而适当引入重复计算可以提高程序的总体性能。
7.顺序程序的特性
⏹顺序性:
处理机严格按照指令次序依次执行,即仅当一条指令执行完后才开始执行下一条指令;
⏹封闭性:
程序在执行过程中独占系统中的全部资源,该程序的运行环境只与其自身动作有关,不受其它程序及外界因素影响;
⏹可再现性:
程序的执行结果与执行速度无关,而只与初始条件有关,给定相同的初始条件,程序的任意多次执行一定得到相同的执行结果.
并发程序特性
⏹交叉性:
程序并发执行对应某一种交叉,不同的交叉可能导致不同的计算结果,操作系统应当保证只产生导致正确结果的交叉,去除那些可能导致不正确结果的交叉;
⏹非封闭性:
一个进程的运行环境可能被其它进程所改变,从而相互影响;
⏹不可再现性:
由于交叉的随机性,并发程序的多次执行可能对应不同的交叉,因而不能期望重新运行的程序能够再现上次运行的结果。
8.解决死锁的方法?
预先分配策略
⏹思想:
进程在运行前一次性地向系统申请它所需要的全部资源,系统能满足呢,则一次性地将所请求的全部资源分配给申请进程;若系统当前不能满足进程的全部资源请求,则就不分配资源,此进程暂不投入运行。
⏹分析:
由于运行前已经获得所需的全部资源,因而在运行期间不会再发出新的资源请求,所以不会发生占有资源又申请资源的现象,破坏了保持申请这一死锁必要条件,故死锁不发生。
⏹缺点:
☐资源利用率低;
☐有些资源有可能根本就不用;
☐有可能发生饥饿。
有序资源分配
⏹将每个资源类按一定原则编号,进程对资源的申请应严格按照资源编号由小到大的次序来进行,即,当进程不占有任何资源时,它可以申请任何资源类ri中的任意多个资源实例,但此后它可以申请另外一个资源类rj中的若干资源实例的充要条件是:
f(ri)☐使用该预防死锁的策略时,资源类的编号应当仔细考虑,为了提高资源的利用率,通常应当按照大多数进程使用资源的次序来给资源类编号,即先使用的或经常使用的排在前面,后使用的或不经常使用的排在后面。
9.事件
事件(Event)是WIN32提供的最灵活的线程间同步方式。
事件存在两种状态:
⏹激发状态(signaledortrue)
⏹未激发状态(unsignalorfalse)
创建事件函数原型
HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset,
BOOLbInitialState,
LPCTSTRlpName
);
10.Win32线程同步的实现(编程)
⏹有三个线程:
主、读、写。
读线程必须在写线程完成写操作之后进行读操作,主线程必须在读线程进行完读操作之后才结束.
HANDLEevRead,evFinish;
VoidReadThread(LPVOIDparam)
{
WaitForSingleObject(evRead,INFINITE);
Cout<<”Reading”<SetEvent(evFinish);
}
VoidWriteThread(LPVOIDparam)
{
Cout<<”Writing”<SetEvent(evRead);
}
Intmain(intargc,char*argv[])
{
evRead=CreateEvent(NULL,FALSE,FALSE,NULL);
evFinish=CreateEvent(NULL,FALSE,FALSE,NULL);
-beginthread(ReadThread,0,NULL);
-beginthread(WriteThread,0,NULL);
WaitForSingleObject(evFinish,INFINITE);
Cout<<”TheProgramisEnd”<Return0;
}
11.数据作用域子句
Intgval=8;全局变量gval是共享的
Voidfuncb(int*x,int*y,intz)环控循制变量i是线程私有的
{cc在并行化语句之外声明,是共享的
Staticintsv;
Intu;temp是循环并行化语句内部的自动变量,是线程私有的
U=(*y)*gval;
*x=u+z;指针变量a、n是共享的
}静态变量sv是共享的,在内存空间中只有一份,这种使用方式会引起数据冲突
Voidfunca(int*a,intn)
{变量u被并行线程调用,是线程私有的
IntI;
Intcc=9;参数x是私有的指针变量,*x指向的内存空间是共享的,实际参数是funca的数组a
#pragmaompparallelfor
For(i=0;iInttemp=cc;参数y是私有的指针变量,指向的*y也是私有的,实际内存空间是temp占用的空间
Funcb(&a[i],&temp,i)参数z是线程私有的
}
}
12.私有变量的初始化和终结操作实例
13.线程私有数据与threadprivate,copyin子句
☐
intcounter=0;
#pragmaompthreadprivate(counter)
voidinc_counter(){
counter++;
}
int_tmain(intargc,TCHAR*argv[]){
#pragmaompparallel
for(inti=0;i<10000;i++)
inc_counter();
printf("counter=%d\n",counter);
}
使用threadprivate子句用来标明某一个变量是线程私有数据,在程序运行的过程中,不能够被其他线程访问到。
⏹
被其他线程访问到。
⏹
使用threadprivate(counter)
不使用threadprivate(counter)
14.线程私有数据与threadprivate,copyin子句
private和threadprivate区别
15.线程私有数据与threadprivate,copyin子句
⏹OpenMP中,主线程之外的线程所拥有的私有全局变量不进行初始化
⏹使用copyin子句对线程私有的全局变量进行初始化。
16.与parallelfor语句的区别:
⏹并行区域采用了复制执行方式,将代码在所有的线程内各执行一次;
⏹循环并行化则是采用工作分配执行方式,将循环需做的所有工作量,按一定的方式分配给各个执行线程,全部线程执行工作的总合等于原先串行执行所完成的工作量。
parallel编译指导语句的执行过程
17.OpenMP支持两种不同类型的线程同步机制
⏹互斥锁的机制
可以用来保护一块共享的存储空间,使得每一次访问这块共享内存空间的线程最多一个,保证了数据的完整性。
互斥的操作针对需要保护的数据而言,在产生了数据竞争的内存区域加入互斥。
⏹事件通知机制
互斥锁保证了多个线程之间的执行顺序。
而事件机制则在控制规定线程执行顺序时所需要的同步屏障(barrier),定序区段(orderedsections),主线程执行(master)等。
18.计算π
规约操作
#include"stdafx.h"
#include"omp.h"
#defineNUM_THREADS2
staticlongnum_steps=100000;
doublex,pi=0.0,sum=0.0,step=1.0/(double)num_steps;
intmain(intargc,_TCHAR*argv[]){
omp_set_num_threads(NUM_THREADS);
#pragmaompparallelforreduction(+:
sum)private(x)
for(longi=0;ix=(i+0.5)*step;
sum+=4.0/(1.0+x*x);
}
pi+=sum*step;
printf("Pi=%f\n",pi);
}
19.静态调度static——不使用size参数
#pragmaompparallelforschedule(static)
for(i=0;i<10;i++){
printf("i=%d,thread_id=%d\n",i,omp_get_thread_num());
}
输出:
线程0得到了0~4次连续迭代,线程1得到5~9次连续迭代
静态调度static——使用size参数
☐#pragmaompparallelforschedule(static,2)
☐for(i=0;i<10;i++){
☐printf("i=%d,thread_id=%d\n",i,omp_get_thread_num());
☐}
输出:
每个线程依次分配到2次连续的迭代计算
动态调度dynamic——不使用size参数
#pragmaompparallelforschedule(dynamic)
for(i=0;i<10;i++){
printf("i=%d,thread_id=%d\n",i,omp_get_thread_num());
}
输出:
动态调度dynamic——使用size参数
#pragmaompparallelforschedule(dynamic,2)
for(i=0;i<10;i++){
printf("i=%d,thread_id=%d\n",i,omp_get_thread_num());
}
输出:
guided调度——不使用size参数
#pragmaompparallelforschedule(guided)
for(i=0;i<10;i++){
printf("i=%d,thread_id=%d\n",i,omp_get_thread_num());
}
输出:
guided调度——使用size参数
#pragmaompparallelforschedule(guided,2)
for(i=0;i<10;i++){
printf("i=%d,thread_id=%d\n",i,omp_get_thread_num());
}
输出:
20.影响性能的主要因素
根据Amdahl定律,我们应当努力提高并行化代码在应用程序中的比率,这是通用的提高效率的方法。
OpenMP本身的开销
OpenMP多线程并行化需要一定的程序库的支持。
在这些运行时库对程序并行加速的同时需要运行库的本身,库中代码的运行必然会带来一定的开销。
负载均衡
要非常注意使得线程之间的负载大致均衡,能够让多个线程在大致相同的时间内完成工作,从而能够提高程序运行的效率。
21.程序性能分析实例:
并行化带来的额外负担
#include"stdafx.h"
#include"windows.h"
#include"omp.h"
int_tmain(intargc,_TCHAR*argv[]){
__int64counter_begin;
__int64counter_end;
__int64diff;
__int64sum=0;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
#pragmaompparallelforreduction(+:
sum)
//for(inti=0;i<2000000000;i++)
for(inti=0;i<10000;i++)
sum+=i;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("sum=%I64dexecutionwithOpenMP,count=%I64d\n",sum,diff);
sum=0;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
//for(inti=0;i<2000000000;i++)
for(inti=0;i<10000;i++)
sum+=i;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("sum=%I64dserialexecution,count=%I64d\n",sum,diff);
return0;
}
22.程序性能分析实例:
负载均衡
#include"stdafx.h"
#include"omp.h"
#include"windows.h"
voidsmallwork(){
}
voidbigwork(){
__int64sum=0;
for(inti=0;i<100000000;i++)
sum+=i;
}
int_tmain(intargc,TCHAR*argv[]){
__int64counter_begin;__int64counter_end;__int64diff;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
#pragmaompparallelfor
for(inti=0;i<100;i++){
if(i<50)smallwork();elsebigwork();
}
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("count=%I64d\n",diff);
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
#pragmaompparallelfor
for(inti=0;i<100;i++){
if(i%2)smallwork();elsebigwork();
}
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("count=%I64d\n",diff);
}
23.程序性能分析实例:
同步开销
#include"stdafx.h"
#include"omp.h"
#include"windows.h"
#defineMAX_COUNT100000000
int_tmain(intargc,TCHAR*argv[]){
__int64counter_begin;
__int64counter_end;
__int64diff;
__int64sum=0;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
for(inti=0;isum+=i;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("sum=%I64dserialexecutioncount=%I64d\n",sum,diff);
sum=0;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
#pragmaompparallelfor
for(inti=0;i#pragmaompcritical
sum+=i;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("sum=%I64dparallelwithcriticalcount=%I64d\n",sum,diff);
sum=0;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
#pragmaompparallelforreduction(+:
sum)
for(inti=0;isum+=i;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_end);
diff=counter_end-counter_begin;
printf("sum=%I64dparallelwithreductioncount=%I64d\n",sum,diff);
}
24.计算π
用矩形法则的数值积分方法来估算π的值:
串行程序
#include"stdafx.h"
#include"windows.h"
staticlongnum_steps=100000;
doublex,pi,sum=0.0,step=1.0/(double)num_steps;
intmain(intargc,_TCHAR*argv[]){
__int64counter_begin;
__int64counter_end;
__int64diff;
QueryPerformanceCounter((LARGE_INTEGER*)&counter_begin);
for(longi=1;i<=num_steps;i++){
x=(i-0.5)*step;
sum+=4.0/(1.0+x*x);
}
printf("Pi=%