针对处理器个数和队列长度之间关系研究手稿(1)

来源:互联网 发布:便宜php空间 编辑:程序博客网 时间:2024/06/13 17:33


本片文章是在性能测试过程中,就发现单线程CPU过高,造成系统卡顿,进行的研究,这篇文章查阅很多网上资料和百度百科中的知识汇编而成。

PDF下载链接:链接: http://pan.baidu.com/s/1slKQeL3 密码: mjng

处理器个数和队列长度之间关系

1 WindowsUNIX

1.1 线程和SMP

Windows的两个重要特征是支持线程和支持对称多处理(SMP),windows支持线程和SMP的下列特征:

  • 操作系统例程可以在任何可以得到的处理器上运行,不同的例程可以在不同的处理器上同时执行。

  • Windows支持在单个进程的执行中使用多个线程。同一个进程可以在不同的处理器上同时执行。

  • 服务器进程可以使用多线程,以处理多个用户同时发出的请求。

  • Windows提供在进程间共享数据和资源的机制以及灵活的进程间通信能力。

1.2 进程和进程控制块

进程以下几个定义:

  • 正在执行的程序

  • 正在计算机上执行的程序实例

  • 能分配给处理器并由处理器执行的实体

  • 具有以下特征的活动单元:一组指令序列的执行、一个当前状态和相关的系统资源集。

也可以把进程当成一组元素组成的实体,进程的两个基本的元素是程序代码和代码相关联的数据集。假设处理器开始执行这个程序代码,且我们把这个执行实体叫进程。在进程执行时,任意一个给定时间,进程都可以唯一地被表征为以下元素:

  • 标识符:跟这个进程相关的唯一标识符,用来区别其他进程。

  • 状态:如果进程正在执行,那么进程处于运行状态

  • 优先级:相对于其他进程的优先级

  • 程序计数器:程序中即将被执行的下一条指令的地址

  • 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针。

  • 上下文数据:进程执行时处理器的寄存器中的数据

  • I/O状态信息:包括显示的I/O请求、分配给进行的I/O设备(例如磁带驱动器)和被进程使用的文件列表等。

  • 记账信息:可能包括处理器时间总和、使用的时钟数总和、时间限制、记账号等。

上述的列表信息存放字进程控制块中(如图3.1所示)的数据结构中,该控制块由操作系统创建和管理。

比较有意义的一点是,进程控制块包含了充分的信息,这样就可以中断一个进程的执行,并且在后来恢复执行进程时就好像进程未被中断过。进程控制块时操作系统能够支持多进程和提供多处理的关键工具。当进程被中断时,操作系统会把程序计数器和处理器寄存器(上下文数据)保存到进程控制块中的相应位置,进程状态也被改变为其他的值,例如阻塞态或就绪态。现在操作系统可以自由地把其他进程设置为运行态,其他进程的程序计数器和进程上下文数据加载到处理器寄存器中,这样其他进程就可以开始执行了。

因此,可以说进程是由程序代码和相关数据还有进程控制块组成。对于一个单处理器计算机,在任何时间都最多只有一个进程在执行,正在执行的这个进程的状态位运行态。


 

2 处理器性能计数指标

2.1 Windows处理器分析方法

处理器(CPU)也可能是系统的瓶颈,如下是针对处理器进行分析的步骤:

  1. 查看System\%Total Processor Time性能计数器的计数值

    该计数值用于体现服务器整体的处理器使用率,对多处理器系统来说,该值体现了所有CPU的平均使用率。该值的数值持续超过90%,则说明整个系统面临着处理器方面的瓶颈,需要通过增加处理器来提高性能。

    PS:由于操作系统本身的特性,在某些多CPU系统中,该数据本身并不大,但若CPU之间的负载情况极为不均衡,也应该视作系统产生了处理器方面的瓶颈。

  2. 查看每个CPUProcessor\%ProcessorTimeProcessor\%UserTimesProcessor\%PrivilegedTime

    Processor\%User Time是指系统的非核心操作系统消耗的CPU时间,如果该值较大,可以考虑是否通过算法优化等方法来降低该值。如果该服务器是数据库服务器,Processor\%User Time值大的原因很可能是数据库的排序或函数操作消耗了过多的CPU时间,此时可以考虑对数据库系统进行优化。

  3. 研究系统处理器瓶颈

    查看System\Processor Queue Length计数器的值,当该计数器的值大于CPU数量的总数加1,说明处理器阻塞。处理器的%Process Time值很高时,一般都伴随着处理器阻塞,但产生处理器阻塞时,Processor\%Process Time计数器的值并不一定很大,此时就需要查看处理器阻塞的原因。

%DPC Time该值越低越好,在多处理器系统中,如果该值大于50%并且Processor\%Process Time值非常高,则考虑加入一个网卡来提高性能。

2.2 Linux/UNIX处理器分析方法

处理器(CPU)也可能是系统的瓶颈,如下是针对处理器进行分析的步骤:

  1. 查看Processor\%Idle Time性能计数器的值

    该计数器描述的是CPU总的空闲时间。如果该值持续低于10%,表明瓶颈是CPU。可以考增加一个处理器或换一个更快的处理器。PS:该计数器的值可以通过top命令输出结果。

   2.查看Processor\%User Time System\%User TimeSystem\CPU context switches

           Processor\%User Time是指系统非内核操作消耗的CPU时间。一般来说如果系统中使用了大量的算法和复杂的计算操作,该值会比较大。System\%User Time系统上所有处理器执行非内核操作的平均时间的百分比,该值反映了用于有用作业上的时间的比率System\CPU context switchesCPU上下文切换。在vmstat的结果中显示为CS例如:CPU 100利用率,那么应该到达这样一个平衡:65%-70 User Time30%-35 System Time0%-5 Idle Time;大量的上下文切换是可以接受的。PS:查看processor信息用top命令,systemvmstat应用

    3.可运行队列

     每个可运行队列不应该超过1-3个线程(每处理器)。 

    PS:Vmstat中一些参数介绍:

        r,可运行队列的线程数,这些线程都是可运行状态,只不过 CPU暂时不可用;

        b,被 blocked的进程数,正在等待 IO请求;

        in,被处理过的中断数

        cs,系统上正在做上下文切换的数目

        us,用户占用 CPU的百分比

        sys,内核和中断占用 CPU的百分比

        wa,所有可运行的线程被blocked以后都在等待 IO,这时候 CPU空闲的百分比

        idCPU完全空闲的百分比

        PS:mpstat输出多个处理器数据:

        CPU使用率过高的进程中的所有线程进行排序的命令:

         ps H -e -o pid,tid,pcpu,cmd --sort=pcpu |grep xxx


 

3单线程CPU过高原因分析

3.1 死循环

        所谓的死循环是无法靠自身的控制总之的循环。死循环我有将它分为:编程死循环、页面死循环、多模块产生的死循环、假死循环。

3.1.1编程死循环

例如在C语言程序中,语句“while(1)printf("*")就是一个死循环,运行它将无休止地打印*号。不存在一种算法,对任何一个程序及相应的输入数据,都可以判断是否会出现死循环。因此,任何编译系统都不做死循环检查。在设计程序时,若遇到死循环,我们可以通过按下Ctrl+Pause/Break的方法,结束死循环。

然而,在编程中死循环并不是一个需要避免的问题,相反,在实际应用中,经常需要用到死循环。例如,我们使用的Windows操作系统下的窗口程序中的窗口都是通过一个叫消息循环的死循环实现的。在单片机、嵌入式编程中也经常要用到死循环。在各类编程语言中,死循环都有多种实现的方法,以C语言为例,可分别使用while.for,goto实现。

死循环的C语言实现:

1while(1);

2for(;;);

3goto

Loop:

...

goto Loop;

3.1.2页面死循环

在网站页面设计当中,导航的设计不可忽视。导航的作用除了给用户寻找相关信息文字性的提示以外。也是增加此页面链接其他页面的入口。如果此页面的导航点击进入时链接地址还是本页面,就会造成死循环。页面死循环不利于网站以及网页的优化。

死循环在系统的应用非常多,也非常重要,所有的应用系统都需要设置一个死循环来保证系统的正常运行,如果没有死循环,那么你会一开机马上就关机,因为这个程序已经运行完毕,所以在系统开发中死循环有着极其重要的作用!

3.1.3死循环举例

C++的例子:

#include<iostream.h>

int main()

{

for(inti=0;;i++)

{

cout<<char(i);

}

return 0;

}

 

C语言死循环的例子:

#include<stdio.h>

main()

{

int a = 0;

while (a <10)

{

printf("%d\n",a);

if (a = 5) {

printf("aequals 5!\n");

}

a++;

}

return 0;

}

      

BASIC语言的死循环:

10PRINT"Infinite Loop"

20 GOTO10'跳到行号=10的位置

 

X86汇编语言的例子:

loop:

; Code to loophere

jmploop

 

Python的例子:

whileTrue:

print("InfiniteLoop")

3.1.4造成死循环的原因

3.1.4.1逻辑错误

以下是一个Visual Basic死循环的例子:

dimxasintegerdountilx> 5'根本不会有x>5的情形x = 1 x = x + 1loop

每一次运行循环时x会先设置为1,然后变为2,因为数值未大于5,所以永远不会退出。若将x = 1由循环内部移到循环之前即可以改善此一情形。

有些程序员可能因为不熟悉特定编程语言的语法而造成死循环,例如以下是一段C语言的程序:

#include<stdio.h>

main()

{

int a=0;

while(a<10)

{

printf("%d\n",a);

if(a=5){//a设定为5,进入无穷回圈printf("aequals 5!\n");}

a++;

}

return0;

}

其预期输出是数字09,其中56中间会显示"a equals 5!",但程序员在编写程序时将设置用的=运算符及判断相同的==运算符弄混了,因此程序会在每次运行循环时都会将a设置为5,因此变量a永远无法到达10,此循环就变成了死循环

3.1.4.2变量处理错误

有时不适当的循环退出条件也可能会造成无预期的死循环,例如以下C语言的例子:

float x=0.1;

while(x!=1.1)

{

//可能会因为浮点运算的误差而出现问题

printf("x =%f\n",x);

x=x+0.1;

}

在有些操作系统中,上述程序会运行10次循环然后退出,但有些系统中,上述程序却可能会一直运行,无法退出,问题主要在循环的退出条件(x != 1.1)要在二个浮点数相等时才退出,结果会依系统处理浮点数的方式而定,只要系统运行10次循环后的结果和1.1差一点点,上述程序就会变成死循环。

若将退出条件改为(x < 1.1)就没有这个问题,程序可能会多运行一次循环,但不会变成死循环。另一种解决方式则是用一个整数变量作为循环变量,再依此变量判断是否要退出循环。

数值分析程序中也可能会出现无预期的死循环,例如程序需一直迭代到误差小于某特定值为止,但若因为运算中的舍去误差,使得误差一直无法小于该特定值,就会产生死循环。

3.1.4.3奥尔德森循环

奥尔德森循环(Alderson loop)是指一个循环有设置退出条件,但因为程序的写法(多半是编程错误),造成永远无法满足退出条件,在针对用户界面程序调试时最容易出现这类的问题。

以下C的伪代码中有一个奥尔德森循环,程序是要计算用户输入一串数字的和,用户输入0时退出循环,但程序中用了不正确的运算符:

sum=0;while(true){printf("Inputa number to add to the sum or 0 to quit");i=getUserInput();if(i*0){//i0为真,则使sum加上i的值sum+=i;//但这不可能发生,因为不论i为何值(i * 0)都是0。如果条件中用的是!=而非*,代码就能正常运行}if(sum>100){break;//终止循环。结束条件存在,但从来没有达到过,因为sum永远不会增加}}

奥尔德森循环是来自一个Microsoft Access的程序员,他编写的程序产生一个有模式的对话框,用户需要回应,程序才能继续运作,但对话框没有OK键或取消键,因此只要此对话窗出现,Access程序就无法继续运作。

3.1.4.4无穷递归

无穷递归是一种由递归造成的死循环。例如以下计算阶乘C语言程序

unsigned intfac(unsigned int a){

//n!=n*(n-1)!

return(fac(a-1)*a);

}

一般递归的程序会有一特定条件,此条件成立时直接计算结果,而不是通过递归来计算结果,若程序中未定义此条件,就会出现无穷递归。

无穷递归会造成堆栈溢出,而无穷递归不会退出,因此也是死循环的一种。不过若递归程序是使用尾部递归的处理方式,在有些编程语言(如Scheme)中会优化成循环,因此不会造成堆栈溢出。

上述的程序可以修改成没有无穷递归的程序。

unsigned intfac(unsigned int a){

if(a==0)

{//定义0!=1

return1;

}

else

{

return(fac(a-1)*a);

}

}

3.1.5多模块死循环

死循环也可能因为多个模块之间的交互而产生。考虑一台服务器若收到无法理解的需求时,会回应错误信息,此架构中不会有死循环。但若有二台上述的服务器(AB),互相交换数据,A收到由B所提交无法理解的需求,会回应错误信息给B,但若B也无法理解A提交的需求(其实是A的错误信息),会再以自己的格式回应错误信息给,A收到后无法理解,会再回应错误信息给B……。像邮件循环就是这类的例子。

3.1.6假死循环

假死循环是指一个循环看似不会退出,但只是一个运行很长时间,最后仍会退出的循环。

以下是一个C语言for循环的程序:

unsigned int i;[2] 

for(i=1;i!=0;i++)

{/* loop code*/}

上述程序每次运行时都将i1,若i等于0时才会退出循环,此程序看似不会退出,但最后还是会退出。程序中型态为unsigned int的变量,其数值有一定上限,当数值已到上限,再加1时,变量数值就会变为0,因此让程序退出。实际的上限值依系统及编译器而不同,假如unsigned int是一个16个比特的字符组,上述的循环会运行65536次。若使用高精度计算,程序会一直运行到存储器无法存储i为止。

 

 

 

3.2 线程中存在死循环

        线程中存在死循环,会导致设备中的CPU使用率过高,引起设备使用不通畅等诸多原因。如何定位线程中的死循环呢?下面给出在UNIXWindows系统下定位死循环的方法。

3.2.1 UNIX定位死循环的方法

        下面举出例子来说明,UNIX系统下定位死循环:

  1. 使用top观察是否存在CPU使用过高现象

  2. CPU使用率过高的进程中的所有线程进行排序,命令如下:

    ps H -e -opid,tid,pcpu,cmd --sort=pcpu |grep xxx

    得到如下结果,其中线程2909使用了7.8%CPU:

        2907 2913 0.0./xxx

                   29072909 7.8 ./xxx

 也可以通过查看/proc中的信息来确定高CPU线程.打印了4,线程ID,线程名,用户时间和内核时间(排名未分先后)

        awk '{print $1,$2,$14,$15}' /proc/2907/task/*/stat

     3.找出调用栈

           使用gdb attach nmsagent所在的进程,gdb中使用 info threads显示所有线程

                   gdb

                   gdb>attach2907

                   gdb>infothreads

得到如下结果,可以发现2909线程的编号是12

13 Thread 0xad5f2b70 (LWP 2908) 0x004ef0d7 in mq_timedreceive () from/lib/tls/i686/cmov/librt.so.1

                12 Thread 0xad58eb70 (LWP2909) 0x006e0422 in __kernel_vsyscall ()

                  11 Thread 0xad52ab70 (LWP2910) 0x006e0422 in __kernel_vsyscall ()

                10 Thread 0xad4f8b70 (LWP2911) 0x006e0422 in __kernel_vsyscall ()

                9 Thread 0xad4c6b70 (LWP2912) 0x006e0422 in __kernel_vsyscall ()

 8Thread 0xad3feb70 (LWP 2913) 0x004ef0d7in mq_timedreceive () from /lib/tls/i686/cmov/librt.so.1

 7Thread 0xace08b70 (LWP 2914) 0x004ef0d7in mq_timedreceive () from /lib/tls/i686/cmov/librt.so.1

                6 Thread 0xac607b70 (LWP2915) 0x006e0422 in __kernel_vsyscall ()

                  5 Thread 0xac5e6b70 (LWP2916) 0x006e0422 in __kernel_vsyscall ()

                4 Thread 0xac361b70 (LWP2917) 0x006e0422 in __kernel_vsyscall ()

                3 Thread 0xac2fdb70 (LWP2918) 0x006e0422 in __kernel_vsyscall ()

 2Thread 0xac1fcb70 (LWP 2919) 0x004ef0d7in mq_timedreceive () from /lib/tls/i686/cmov/librt.so.1

                   *1 Thread 0xb78496d0 (LWP 2907) 0x006e0422 in __kernel_vsyscall ()

使用thread切换线程,使用bt显示线程栈

                   gdb>thread12

                   gdb>bt

得到如下线程栈

                   #0 0x006e0422 in __kernel_vsyscall ()

                   #1 0x001cca26 in nanosleep () from/lib/tls/i686/cmov/libc.so.6

                   #2 0x001fc2dc in usleep () from/lib/tls/i686/cmov/libc.so.6

                   #3 0x0806b510 in OspTaskDelay ()

                   #4 0x0805c710 inCDispatchTask::NodeMsgSendToSock() ()

                   #5 0x0805cc74 in DispatchTaskEntry ()

                   #6 0x0806a8e9 in OspTaskTemplateFunc(void*) ()

#7 0x00d4780e in start_thread () from/lib/tls/i686/cmov/libpthread.so.0

                  #8 0x002027ee in clone () from/lib/tls/i686/cmov/libc.so.6

 

            4. ps+ strace

得到进程ID 21465

ps -e |grep cmu

 4996 ?       00:00:25 cmu_fjga_sp3

21465 pts/5   00:08:10 cmu

得到线程时间,其中最占CPU的是 EpollRecvTask 21581

ps -eL |grep 21465

21465 21579 pts/500:00:00 CamApp

21465 21580 pts/500:00:00 TimerMan Task

21465 21581 pts/500:09:02 EpollRecvTask

21465 21582 pts/500:00:00

使用 strace -p 21581得到线程栈

3.2.2 Windows定位死循环的方法

下面以java为例来说明,Windows系统下定位死循环:

  1. 找到java进程对应的pid

    pid的方法是:打开任务管理器,然后点击 "查看"菜单,然后点击 "选择列",把pid勾上,然后就可以在任务管理器里面看到所有进程的pid值了。(也可以用第三步中提到的工具直接查看)

     

  2. 然后把java进程导出快照。直接运行命令

    我这里是指定把java所有的信息导出到c盘的31372.stack的文件里。

    jstack -l 31372 > c:/31372.stack  

  3. windows下只能查看进程的cpu占用率,要查看线程的cpu占用率要借助其他的工具,我这里用的是微软提供的 ProcessExplorer v15.3

    下载地址http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx

    下载完后解压运行,右键点击需要查看的进程---properties

  4. 然后选择 Threads选项卡,找到占用cpu的线程的tid,比如我这里是 31876 的线程

  5. pid转换成16进制,我这里直接用系统自带的计算器转换,置于为什么要转换,是因为先前用jstack导出的信息里面线程对应的tid16进制的。

     6.在 c盘的31372.stack文件中查找 7C84
  1. 7.由于是我的程序已经该过了,这里没有异常的东西,所以这里没有什么异常内容。

问题没解决之前,找到到这里的内容为:

"Thread-23"prio=6 tid=0x03072400 nid=0x1b68 runnable [0x0372f000]

  java.lang.Thread.State: RUNNABLE

at com.horn.util.MyEncrypt.encode(MyEncrypt.java:17)

at com.horn.common.OrderUtil.hisExp(OrderUtil.java:228)

at com.horn.util.MsgManage.receiveMsg(MsgManage.java:961)

at com.horn.util.PollMessageThread.run(PollMessageThread.java:74)

 

 Locked ownable synchronizers:

        - None

于是打开 t com.horn.util.MyEncrypt.encode(MyEncrypt.java:17)

分析了下代码,问题找到了。

//100-999的随机数

intrandom = (int) (Math.random() * 1000);

while(random < 100) {

   random = random * 10;

}

关键在于 Math.random()的取值范围是大于0小于1是吧?
如果Math.random()的值为 0.00009以下...就成死循环了...

现在修改为了:

//100-999的随机数

int random = newRandom().nextInt(900) + 100;

3.3线程太多

多处理器系统也只有一个待运行的线程队列,内存中也只有一个操作系统拷贝,而且也只有一个内存系统,但是会有多个cpu同时运行不同的线程。一个cpu运行一个线程,那么上图中的系统最多能在同一时间运行2个线程。其实,多处理系统需要掌握的知识不是这些,而是缓存一致性
      
现在来解释下什么是缓存一致性。由于,还是只有一个内存系统。所有cpu都要和这个内存系统通信,但是只有一条总线,那么这无疑会造成总线紧张,限制整体的速度了。那么,你多个cpu也没多少意义了。解决这个问题的办法还是利用cpu的缓存机制,学过组成原理的同学都知道,cpu的缓存命中率还是很高的,有90%以上吧。那么,我继续利用缓存机制还是可以降低总线的频繁使用的。但是,每个cpu都有自己的缓存。如果有2cpu的缓存存储的是同一内存数据的内容,其中一个cpu的缓存更新了,另外一个cpu的缓存也必须更新,这就是所谓的缓存一致性。编程多线程程序的一个很重要的一点就是避免因为缓存一致性引起的缓存更新风暴。
      
cpu不能成倍提高速度的原因是任务的某些部分是必须串行处理的。比如,矩阵乘法可以分为三个部分,初始化矩阵,相乘,返回结果。这三部分第二部分可以用多线程来处理,第一部分和第三部分则是不可以的。而且第二部分必须在第一部分完成之后,第三部分必须在第一部分完成之后。那么,无论你添加多少个处理器,最快的时间都至少是第一部分和第二部分的时间之和。这个事实叫做Amdahl法则。
  
如果使用多线程,那么就必须考虑线程同步,而线程同步又是导致速度降低的关键。所以下面就讲述一些方法来加快多线程程序的吞吐速度。
  
方法一,把一个任务分解为多个可以子任务。
  
因为总有些子任务是可以并发的,多个子任务并发执行了很可能就能够避免cpu需要io操作的完成了,而且能够提高系统的吞吐量。
  
方法二,缓存多线程的共享数据。
  
当你已经在使用多线程了,很多时候必须使用共享数据。如果,数据是只读的,那么可以在第一次获取后保存起来,以后就可以重复使用了。但是,第一次的获取还是无法避免的需要线程同步操作的。
  
方法三,如果线程数目有限,就不要共享数据。
  
做法是为每一个线程实例化一个单独的数据,其实就是为每一个线程分配一块数据使用。这样没有线程同步操作了,速度可以尽可能的提示。
  
方法四,如果没办法确定线程数目到底有多少,那么使用部分共享吧。

部分共享其实就是使用多个资源池代替一个资源池,资源池的数目得更加经验来确定

 

最后在提一个叫做Thundering Herd的问题,该问题维基百科有定义http://en.wikipedia.org/wiki/Thundering_herd_problem。大意是,当多个线程在等待一个资源的时候,如果事件等待到了,操作系统是唤醒所有等待的线程让它们自己去竞争资源了还是选择一个线程把资源给它。当然唤醒所有的线程肯定开销要大,而且所有没有抢到资源的线程还得重新进入等待状态,这无疑造成很多没必要的操作,浪费了没必要的线程上下文切换。总之,会不会存在Thundering Herd还是跟不同的操作系统有关的。万一存在ThunderingHerd了,多线程可能就没那么好办了。

附录3

Amdahl法则

背景

        随着从存储系统的日益复杂,对存储系统的性能分析就显得非常必要。单个存储器的性能由生产厂商在数据手册中说明,那么由多个存储体构成的并行存储系统的性能如何分析,尤其是系统的吞吐率及相应时间就必须借助数学模型进行分析,或者再利用软件进行仿真确定。对于更全面的系统仿真将花费非常大的代价,因此实际上多采用数学模型的方式确定。为此,利用阿姆达尔定律来说明存储系统性能分析模型。[

简介

       Amdahl加速定律的基本出发点是:1.对于很多科学计算,实时性要求很高,即在此类应用中时间是个关键因素,而计算负载是固定不变的。为此在一定的计算负载下,为达到实时性可利用增加处理器数来提高计算速度;2.因为固定的计算负载是可分布在多个处理器上的,这样增加了处理器就加快了执行速度,从而达到了加速的目的。在此意义下,1967Amdahl推导出了固定负载的加速公式。[2] 

阿姆达尔定律是计算机系统设计的重要定量原理之一,于1967年由IBM360系列机的主要设计者阿姆达尔首先提出。该定律是指:系统中对某一部件采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。阿姆达尔定律实际上定义了采取增强(加速)某部分功能处理的措施后可获得的性能改进或执行时间的加速比。简单来说是通过更快的处理器来获得加速是由慢的系统组件所限制。

阿姆达尔曾致力于并行处理系统的研究。对于固定负载情况下描述并行处理效果的加速比s,阿姆达尔经过深入研究给出了如下公式:

S=1/(1-a+a/n)

其中,a为并行计算部分所占比例,n为并行处理结点个数。这样,当1-a=0时,(即没有串行,只有并行)最大加速比s=n;当a=0时(即只有串行,没有并行),最小加速比s=1;当n→∞时,极限加速比s→ 1/1-a),这也就是加速比的上限。例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4。这一公式已被学术界所接受,并被称做阿姆达尔定律,也称为安达尔定理”(Amdahl law)

并行存储性能中的阿姆达尔定律

       并行存储系统的性能分析可以通过简单的性能模型展开,这其中主要就是阿姆达尔定律。阿姆达尔定律是一个非常简单而通用的并行处理性能模型。这里以程序的并行运行为例说明该定律,其他情况比较容易进行推推导分析。由于单个程序内部不一定全部可以实现并行处理。那么假设f为程序中必须进行串行操作的部分比率,其余的1-f部分则认为可以完全在硬件上并行执行,而且P表示硬件的并行度。P在不同形式的计算机系统中代表着不同的含义:[2] 

1)在MIMD系统中,P是处理器的数目;

2)在SIMD系统中,P是正在处理的数据数目;

3)在流水方式工作的SIMD系统中,P是矢量速度和标量速度的比;

4)在流水方式工作的MIMD系统中,P是流水线功能段的数目。

为了在并行度p的硬件上获得50%以上的效率,穿行操纵部分比率不能超过1/P-1)。随着P的增加,相应条件也越来越难以满足。最早应用Amdahl定律对串行处理进行性能评价,但是在各种并行工作环境中Amdahl定律也非常有用。例如:在并行度很高的系统中,轻微的非并行代码会对系统带来很大的影响;快速的矢量处理器必须具有一个快速的标量处理器以获得其峰值性能的相当大部分能力;对当前穿行执行的小部分代码进行并行完成也可以增加相当大的效率。

虽然Amdahl定律建立了一个非常简单的行囊模型,但也不能过于简单化使用。系统规模,特别是处理器数目P通常为了处理更大的问题而不断增加,而通常问题的规模的增加并不能显著增加串行工作量。这种情况下,f与问题大小成反比。如果问题大小岁并行度P增加而增大,那么随着问题和系统规模的增大,性能表现呈现出收缩性。在计算机系统中,Amdahl定律也可表述为,对系统内某部分的并行化改进造成的整体性能提升量取决于该部分在整体过程中执行的时间,即经常性事件或其部分的改进造成的整体性能得到较大提升。加速比也可以表示为使用改进方式完成整个任务时间的比值。实际上有两个主要因素影响加速比,第一个因素是需要改进提高速度部分在总执行时间中所占的比值。实际上有两个主要因素影响加速比,第一个因素是需要改进提高速度部分在总执行时间中所占的比例。譬如,1s完成的人物其中有200ms可以改进,那么该比例救赎20%,可以称为改进比例。第二个因素是采用改进方式后相应部分速度提高的程度。可以用未改进情况下该部分执行时间与改进后执行时间的比值衡量。改进加速比一般都大于1。那么改进后的任务总时间为没有改进的部分执行时间和改进部分执行时间的和。

 

hundering Herd的问题

        维基百科有定义http://en.wikipedia.org/wiki/Thundering_herd_problem

 


4处理器个数和队列长度研究



4.1 处理器的定义和主要功能

        中央处理器(CPU,英语:CentralProcessing Unit),是电子计算机的主要设备之一,电脑中的核心配件。其功能主要是解释计算机指令以及处理计算机软件中的数据。电脑中所有操作都由CPU负责读取指令,对指令译码并执行指令的核心部件。它的主要功能有:指令顺序控制、操作控制、时间控制、数据加工。

4.2多线程与多核

多核()处理器是指在一个处理器上集成多个运算核心从而提高计算能力,也就是有多个真正并行计算的处理核心,每一个处理核心对应一个内核线程。内核线程(Kernel Thread KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。一般一个处理核心对应一个内核线程,比如单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。

现在的电脑一般是双核四线程、四核八线程,是采用超线程技术将一个物理处理核心模拟成两个逻辑处理核心,对应两个内核线程,所以在操作系统中看到的CPU数量是实际物理CPU数量的两倍,如你的电脑是双核四线程,打开“任务管理器\性能”可以看到4CPU的监视器,四核八线程可以看到8CPU的监视器。

 超线程技术就是利用特殊的硬件指令,把一个物理芯片模拟成两个逻辑处理核心,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU的闲置时间,提高的CPU的运行效率。这种超线程技术(如双核四线程)由处理器硬件的决定,同时也需要操作系统的支持才能在计算机中表现出来。 程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight ProcessLWP),轻量级进程就是我们通常意义上所讲的线程(我们在这称它为用户线程),由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。用户线程与内核线程的对应关系有三种模型:一对一模型、多对一模型、多对多模型。

4.2.1一对一模型

对于一对一模型来说,一个用户线程就唯一地对应一个内核线程(反过来不一定成立,一个内核线程不一定有对应的用户线程)。这样,如果CPU没有采用超线程技术(如四核四线程的计算机),一个用户线程就唯一地映射到一个物理CPU的线程,线程之间的并发是真正的并发。一对一模型使用户线程具有与内核线程一样的优点,一个线程因某种原因阻塞时其他线程的执行不受影响;此处,一对一模型也可以让多线程程序在多处理器的系统上有更好的表现。但一对一模型也有两个缺点:1.许多操作系统限制了内核线程的数量,因此一对一模型会使用户线程的数量受到限制;2.许多操作系统内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率下降

4.2.2多对一模型

多对一模型将多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来进行,因此相对一对一模型,多对一模型的线程切换速度要快许多;此外,多对一模型对用户线程的数量几乎无限制。但多对一模型也有两个缺点:1.如果其中一个用户线程阻塞,那么其它所有线程都将无法执行,因为此时内核线程也随之阻塞了;2.在多处理器系统上,处理器数量的增加对多对一模型的线程性能不会有明显的增加,因为所有的用户线程都映射到一个处理器上了。

4.2.3多对多模型

多对多模型结合了一对一模型和多对一模型的优点,将多个用户线程映射到多个内核线程上。多对多模型的优点有:1.一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行;2.多对多模型对用户线程的数量没有限制;3.在多处理器的操作系统中,多对多模型的线程也能得到一定的性能提升,但提升的幅度不如一对一模型的高。在现在流行的操作系统中,大都采用多对多的模型。

4.3 处理器个数和队列长度猜想

        随着科技的发展,计算机的核数也越来越多,从之前的单核到现在的多核。在最近做测试过程中,发现一个现象非常有趣:在压了测试过程中,系统有8个核,如果按每个处理器处理1-3个线程,那么队列的最大值应该在24个;然而在进行压力测试过程中,某个核的使用率非常高,持续在90%以上,但队列长度正常,没出现堵塞状况,但是程序会出现卡顿现象!

        针对上述猜想现象,我存在如下猜想:

  • 猜想1处理器活跃个数和队列长度存在一定的指数关系。假设,一个处理器处理一个进程,那么处理器的活跃个数和队列应该成线性关系,存在一个关键因子K值和初始值b,我猜想应该出现如下公式:y=Kx+bx代表处理器处理器活跃个数,y代表队列长度。

  • 猜想2不同的操作系统,处理器的优先级和处理速度不同,引用软件在进入操作系统时,操作系统会根据内部调度算法综合处理器优先级和队列长度来选择相对于的处理器。

 

 

 



0 0
原创粉丝点击