海阔天空

I'm on my way!
随笔 - 17, 文章 - 69, 评论 - 21, 引用 - 0
数据加载中……

程序性能分析

这篇文章主要是想谈谈在以CPU为中心的计算体系结构中影响程序性能的主要因素和性能的分析方法以及多线程对程序性能的影响,读这篇文章首先要具备一定的体系结构和操作系统基础,特别是进程调度,建议看《Operation System Concept》(中文《操作系统概论》)。
 
先定义一下程序的性能,就是在单位时间内能执行的任务数或者执行某个任务需要的时间。显然,在更短的时间内执行更多的任务性能就越高。
 

CPUIO操作

 
言归正传,先看一个经典的入门的C程序Hello World!

int main(int argc, char * args[])
{
    int m = 0;
    for (int i = 0;i < 10;i ++)
    {
        m = m+i;
    }

    printf("Hello World! 1+2+…+10=%d\n", m);

    return 0;
}

这个不算原始的经典的Hello World,比那个Hello World稍微复杂了点,加了一个循环,用来计算1+2+3…+10的值。
 
如果有了操作系统进程调度的基础,可以知道这个程序分成两段执行,第一段是计算1+2+…+10的值,主要在CPU(中央处理单元)中进行,C代码:

    int m = 0;
    for (int i = 0;i < 10;i ++)
    {
        m = m+i;
    }

第二段是将计算结果输出到控制台的这段,将一串文本通过显卡驱动,传送到显示器上显示,主要在显卡上进行,C代码:

printf("Hello World! 1+2+…+10=%d\n", m);

整个程序顺序执行,所以CPU先计算完成得到1+2+…+10的值后,将这个值转换成一串字符串,然后将字符串发送给显示器,等待显示器显示完成后,整个程序结束,如果将CPU执行表示为蓝色,将显示器执行表示为红色,那么程序执行流程如下:
500)this.width=500;" border="0">
1
假设CPU中计算1+2+…+10和将这个值变成字符串花费了11ns(纳秒),而显卡将字符串显示到显示器上花费了7ns,那么整个程序运行花费了18ns
 
Hello World是最简单的程序,也是所有其他程序的基础,在以CPU为中心的计算机结构中,内存负责程序的存储,CPU负责程序的运算和流程控制,其他元件被看成跟上面Hello World中显卡类似的外围设备,也被称作IO设备,所以任何程序都可以看作是一系列CPU操作和一系列IO操作的符合体,如下图所示:
500)this.width=500;" border="0" width="500">
2
所以影响程序性能的主要因素有两个方面:一是CPU操作的快慢,二是IO操作的快慢。
 
所以程序性能分析的主要方法就是正确区分哪些是CPU操作,哪些是IO操作。
 
CPU操作通常有这些:
赋值和计算,如:m = i*j;
流程控制,如:while(true) { i ++;}
 
IO操作通常有这些:
磁盘文件操作。
网络操作。
键盘和鼠标操作。
显卡操作,如在屏幕上绘图,显示文本等。
USB操作。
串口操作。
红外线操作。
磁带机操作。
通常除CPU和内存外的其他设备都可以看成IO操作,内存之所以不看作IO设备,是因为内存访问相对IO而言,通常要快几个数量级,所以像char * buff = new char[100];这样的操作通常也看作CPU操作。
 
通过分析划分出程序的CPU操作和IO操作程序段后,可以有针对性的进行优化。
 
对于CPU操作,常用的提升性能的方法是优化计算和流程控制代码,如相乘计算 m = i * 8,可以使用 m = i << 3,因为位操作比乘法操作速度快,通常在某种语言中都会讲到程序的优化,就属于优化CPU操作速度。
对于IO操作,如果IO操作过于频繁而成为系统瓶颈,可以清除一些不必要的IO操作,也可以更换速度更快的IO设备来提高速度,如把硬盘从5400转提升到7200转。
 

多线程

 
下面看看多线程对程序性能的影响,什么时候该使用多线程,什么时候使用多线程达不到预期的效果。
多线程是程序里面有像上面那样的多个执行流程,这些执行流程独立或者联合起来完成某些任务。
先看看计算机只有一个CPU,一个IO设备,程序有两个线程,两个线程执行同样的代码,可以画出执行流程:
500)this.width=500;" border="0" width="500">
3
线程1按正常的执行流程执行,线程2虽然跟线程1执行同样的代码,却出现很多不连续的片段,比如2.2à2.32.4à2.5,这是因为只有一个CPU,所以CPU在进行线程1CPU操作时,不能同时进行线程2CPU操作,也就是2.42.5本来是跟线程11.3代码一样,但是却被CPU分两次执行,因为CPU正在执行1.72.22.3也是同样的道理,因为IO设备要执行1.4的代码,所以2.22.3被打断。但是两个线程的CPU操作和IO操作在时间上可以重叠,因为他们是不同的设备。
也就是在时间上,CPUIO设备只能同时做一件事情,CPUIO设备可以各自做自己的事情。
考察一种极端的情况,假设某个程序没有IO操作,只有CPU操作,那么流程图变成:
500)this.width=500;" border="0" width="500">
4
线程1将占用所有的CPU时间,线程2将一直等待直到线程1完成,因为线程1完成任务后,依然可以再次执行任务,所以这时使用线程1完成任务和使用线程2完成任务没有区别,也就是线程2的存在并不会让程序多完成一些任务,所以线程2的存在,并不能提升程序性能。
所以,如果一个程序只有CPU操作,那么多线程并不能提升程序性能。
同理,如果一个程序只有IO操作,那么多线程并不能提升程序性能。
 
但是多线程在现实中确实有提高程序性能的时候,那是因为实际的程序像图3那样,有CPU操作和IO操作组成,CPU操作和IO操作在时间上可以重叠,所以,同一时间内,程序可以做更多的事情。
如果一个线程中CPU操作时间为MIO操作时间为N,那么在单位时间内,平均有M/(M+N)在处理CPU操作,有N/(M+N)的时间CPU空闲,如果要让CPU充分利用,那么可以增加(N/(M+N))/(M/(M+N))=N/M个线程来填补CPU操作的空白,这样CPU100%被利用,如果线程再增加,CPU没有空闲,几乎不会增加程序性能。
所以,让CPU 100%利用的线程最大数为1+N/M
同你,让IO设备100%利用的线程最大数为1+M/N
这两个公式只是一个度量式,不是一个计算式,因为随着线程数的增加,CPU操作时间和IO操作时间将会随着变化,MN不再固定。
 
看两种常用的程序,服务器程序和用户交互程序。
服务器程序通常提供某种网络服务,如WEB服务器,这种程序要求能最大化的利用CPUIO,在单位时间内处理尽可能多的任务,所以应该使用尽可能时CPUIO都满符合工作,多线程数可以取1+N/M1+M/N中较小的值,如果观察服务器的CPUIO使用率,会发现他们常常接近90%
用户交互程序通常根据用户的某些输入进行相应的操作,操作完成再次等待用户输入,如Microsoft Word,要求对用户的输入能及时反应,所以操作线程的CPU操作和IO操作应该有一定的空闲,使得用户输入线程能随时获取CPU来响应用户的输入,使用Microsoft Windows时,打开任务管理器,可以发现CPU使用率常常很低,如1%20%
 

IO复用

 
从上面的分析可以看出,多线程提升程序性能,主要得益于让CPUIO设备能并行操作,另一种让CPUIO设备并行操作的方法是IO复用,基本的思想是需要进行IO操作时,只是发送一个IO操作请求给IO设备而不必等待IO完成,CPU操作可以继续进行,IO操作完成后通过某种方法如事件通知程序,然后程序做相应的处理,流程如下:
500)this.width=500;" border="0" width="500">
5
以前需要18ns执行的程序,现在只需要11ns就可以完成,性能提升。
常用的文件异步操作、网络异步操作都属于IO复用。
使用IO复用后,程序通常只需要一个线程就可以完成所有的功能,减少操作系统线程间切换的开销,并且不需要线程间同步,但是IO复用需要使用特定的方法监视IO状态,开发相对比较复杂。
Window 2000IOCPIO Complete Port)就是基于IO复用的思想。

总结

虽然上面的结论是在一个CPU并且没有考虑操作系统的进程调度和内存管理等因素的影响的前提下得出的,但是在以CPU为中心的计算机体系结构中,CPU操作和IO操作的划分确实普遍适用的,进程调度和内存管理本身也可以看成是CPU操作和IO操作复合的程序,对于多CPU的系统和多IO设备的系统,分析的基础是所有这些设备能并行操作,所以上面得出的结论是普遍适用的。
在分析过程中,对很多结论使用了粗体字,是为了醒目,不要死记硬背,要记住的是基本原理和分析方法,这样才能放之四海而皆准。


转自:http://blog.chinaunix.net/u1/52224/showart_417513.html

posted on 2009-07-27 21:37 石头@ 阅读(2107) 评论(0)  编辑  收藏 所属分类: 基础技术


只有注册用户登录后才能发表评论。


网站导航: