性能与伸缩性

使用线程的一种说法是为了提高性能。多线程可以使程序充分利用闲置的资源,提高资源的利用率,同时能够并行处理任务,提高系统的响应性。 但是很显然,引入线程的同时也引入了系统的复杂性。另外系统的性能并不是总是随着线程数的增加而总是提高。

 

性能与伸缩性

性能的提升通常意味着可以用更少的资源做更多的事情。这里资源是包括我们常说的CPU周期、内存、网络带宽、磁盘IO、数据库、WEB服务等等。 引入多线程可以充分利用多核的优势,充分利用IO阻塞带来的延迟,也可以降低网络开销带来的影响,从而提高单位时间内的响应效率。

为了提高性能,需要有效的利用我们现有的处理资源,同时也要开拓新的可用资源。例如,对于CPU而言,理想状况下希望CPU能够满负荷工作。当然这里满负荷工作是指做有用的事情,而不是无谓的死循环或者等待。受限于CPU的计算能力,如果CPU达到了极限,那么很显然我们充分利用了计算能力。对于IO而言(内存、磁盘、网络等),如果达到了其对于的带宽,这些资源的利用率也就上去了。理想状况下所有资源的能力都被用完了,那么这个系统的性能达到了最大值。

为了衡量系统的性能,有一些指标用于定性、定量的分析。例如服务时间、等待时间、吞吐量、效率、可伸缩性、生成量等等。服务时间、等待时间等用于衡量系统的效率,即到底有多快。吞吐量、生成量等用于衡量系统的容量,即能够处理多少数据。除此之外,有效服务时间、中断时间等用于能力系统的可靠性和稳定性等。

可伸缩性的意思是指增加计算资源,吞吐量和生产量相应得到的改进。 从算法的角度讲,通常用复杂度来衡量其对应的性能。例如时间复杂度、空间复杂度等。

 

Amdahl定律

并行的任务增加资源显然能够提高性能,但是如果是串行的任务,增加资源并不一定能够得到合理的性能提升。 Amdahl定律描述的在一个系统中,增加处理器资源对系统行的提升比率。 假定在一个系统中,F是必须串行化执行的比重,N是处理器资源,那么随着N的增加最多增加的加速比:

 

理论上,当N趋近于无穷大时,加速比最大值无限趋近于1/F。 这意味着如果一个程序的串行化比重为50%,那么并行化后最大加速比为2倍。

加速比除了可以用于加速的比率外,也可以用于衡量CPU资源的利用率。如果每一个CPU的资源利用率为100%,那么CPU的资源每次翻倍时,加速比也应该翻倍。 事实上,在拥有10个处理器的系统中,程序如果有10%是串行化的,那么最多可以加速1/(0.1+(1-0.1)/10)=5.3倍,换句话说CPU的利用率只用5.3/10=53%。而如果处理器增加到100倍,那么加速比为9.2倍,也就是说CPU的利用率只有个9.3%。

显然增加CPU的数量并不能提高CPU的利用率。下图描述的是随着CPU的数量增加,不同串行化比重的系统的加速比。

 

很显然,串行比重越大,增加CPU资源的效果越不明显。

 

性能提升

性能的提升可以从以下几个方面入手。

 

系统平台的资源利用率

一个程序对系统平台的资源利用率是指某一个设备繁忙且服务于此程序的时间占所有时间的比率。从物理学的角度讲类似于有用功的比率。简单的说就是:资源利用率=有效繁忙时间/总耗费时间。

也就说尽可能的让设备做有用的功,同时榨取其最大值。无用的循环可能会导致CPU 100%的使用率,但不一定是有效的工作。有效性通常难以衡量,通常只能以主观来评估,或者通过被优化的程序的行为来判断是否提高了有效性。

 

延迟

延迟描述的是完成任务所耗费的时间。延迟有时候也成为响应时间。如果有多个并行的操作,那么延迟取决于耗费时间最大的任务。

 

多处理

多处理是指在单一系统上同时执行多个进程或者多个程序的能力。多处理能力的好处是可以提高吞吐量。多处理可以有效利用多核CPU的资源。

 

多线程

多线程描述的是同一个地址空间内同时执行多个线程的过程。这些线程都有不同的执行路径和不同的栈结构。我们说的并发性更多的是指针对线程。

 

并发性

同时执行多个程序或者任务称之为并发。单程序内的多任务处理或者多程序间的多任务处理都认为是并发。

 

吞吐量

吞吐量衡量系统在单位之间内可以完成的工作总量。对于硬件系统而言,吞吐量是物理介质的上限。在没有达到物理介质之前,提高系统的吞吐量也可以大幅度改进性能。同时吞吐量也是衡量性能的一个指标。

 

瓶颈

程序运行过程中性能最差的地方。通常而言,串行的IO、磁盘IO、内存单元分配、网络IO等都可能造成瓶颈。某些使用太频繁的算法也有可能成为瓶颈。

 

可扩展性

这里的可扩展性主要是指程序或系统通过增加可使用的资源而增加性能的能力。

 

线程开销

假设引入的多线程都用于计算,那么性能一定会有很大的提升么? 其实引入多线程以后也会引入更多的开销。

 

切换上下文

如果可运行的线程数大于CPU的内核数,那么OS会根据一定的调度算法,强行切换正在运行的线程,从而使其它线程能够使用CPU周期。

切换线程会导致上下文切换。线程的调度会导致CPU需要在操作系统和进程间花费更多的时间片段,这样真正执行应用程序的时间就减少了。另外上下文切换也会导致缓存的频繁进出,对于一个刚被切换的线程来说,可能由于高速缓冲中没有数据而变得更慢,从而导致更多的IO开销。

 

内存同步

不同线程间要进行数据同步,synchronized以及volatile提供的可见性都会导致缓存失效。线程栈之间的数据要和主存进行同步,这些同步有一些小小的开销。如果线程间同时要进行数据同步,那么这些同步的线程可能都会受阻。

 

阻塞

当发生锁竞争时,失败的线程会导致阻塞。通常阻塞的线程可能在JVM内部进行自旋等待,或者被操作系统挂起。自旋等待可能会导致更多的CPU切片浪费,而操作系统挂起则会导致更多的上下文切换。

了解了性能的提升的几个方面,也了解性能的开销后,应用程序就要根据实际的场景进行取舍和评估。没有一劳永逸的优化方案,不断的进行小范围改进和调整是提高性能的有效手段。当前一些大的架构调整也会导致较大的性能的提升。

简单的原则是在保证逻辑正确的情况小,找到性能瓶颈,小步改进和优化。

 

参考资料

深入浅出 Java Concurrency (40): 并发总结 part 4 性能与伸缩性[转]的更多相关文章

  1. 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap

    (转自:http://blog.csdn.net/fg2006/article/details/6404226) 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器 ...

  2. 深入浅出 Java Concurrency (21): 并发容器 part 6 可阻塞的BlockingQueue (1)[转]

    在<并发容器 part 4 并发队列与Queue简介>节中的类图中可以看到,对于Queue来说,BlockingQueue是主要的线程安全版本.这是一个可阻塞的版本,也就是允许添加/删除元 ...

  3. 深入浅出 Java Concurrency (16): 并发容器 part 1 ConcurrentMap (1)[转]

    从这一节开始正式进入并发容器的部分,来看看JDK 6带来了哪些并发容器. 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器,Collections.synchro ...

  4. 深入浅出 Java Concurrency (27): 并发容器 part 12 线程安全的List/Set[转]

    本小节是<并发容器>的最后一部分,这一个小节描述的是针对List/Set接口的一个线程版本. 在<并发队列与Queue简介>中介绍了并发容器的一个概括,主要描述的是Queue的 ...

  5. 深入浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque[转]

    这个小节介绍Queue的最后一个工具,也是最强大的一个工具.从名称上就可以看到此工具的特点:双向并发阻塞队列.所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条 ...

  6. 深入浅出 Java Concurrency (17): 并发容器 part 2 ConcurrentMap (2)[转]

    本来想比较全面和深入的谈谈ConcurrentHashMap的,发现网上有很多对HashMap和ConcurrentHashMap分析的文章,因此本小节尽可能的分析其中的细节,少一点理论的东西,多谈谈 ...

  7. 深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱

    常见的并发陷阱 volatile volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的.参考指令重排序 volatile最常见于下面两种场景. a. 循环 ...

  8. 深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]

    常见的并发场景 线程池 并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量. 最常见.比较复杂一个场景是Web容器的线程池.Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复 ...

  9. 深入浅出 Java Concurrency (37): 并发总结 part 1 死锁与活跃度[转]

    死锁与活跃度 前面谈了很多并发的特性和工具,但是大部分都是和锁有关的.我们使用锁来保证线程安全,但是这也会引起一些问题.   锁顺序死锁(lock-ordering deadlock):多个线程试图通 ...

随机推荐

  1. drools原生drl规则文件的使用

    在初识drools中对drl文件进行了简单的介绍.这里举个例子来具体说明下.主要是写了规则之后我们如何用java代码来run起来. drl文件内容如下: rule "ageUp12" ...

  2. The linux command 之软件包管理

    一.软件包系统 不同的Linux发行版用的是不同的软件包系统,多数都采用以下两种: 二.软件包系统工具 三.在库里查找软件包 四.安装库中的软件包 五.安装软件包文件中的软件包 六.删除软件包 七.更 ...

  3. Centos6 安装完之后,没有网络

    Virtualbox安装的centos 6.10的虚拟机,安装时,网络是NAT网络,安装完之后,将网络改为桥接网卡,启动虚拟机之后,使用 ifconfig 命令查看没有到eth0的信息,只有127.0 ...

  4. 在vue中使用高德地图vue-amap

    1.安装 vue-amap我安装指定版本0.5.10的版本 npm i --save vue-amap@0.5.10 2.main.js中的配置 key申请地址教程:https://lbs.amap. ...

  5. JavaScript 数据值校验工具类

    /** * 数据值校验工具类 */ var checkService = { // 不校验 none: function () { return true; }, //非空校验 isEmpty: fu ...

  6. 线段树逆序对(偏序)——cf1187D好题!

    /* 排除掉所有不可能的情况,剩下的就是可行的 1.数的数量不相同 2.对任意一个区间进行排序,等价于可以交换任意逆序对, 那么从1到n扫描b数组,判断是否可以将a数组中等于b[i]的值所在的位置j交 ...

  7. eclipse+terminal

    eclipse 怎么安装terminal插件   1 首先打开eclipse,找到help菜单,点击Eclipse Marketplace. 2 在search框里输入Terminal,点击Go查找. ...

  8. System.Web.Mvc.RoutePrefixAttribute.cs

    ylbtech-System.Web.Mvc.RoutePrefixAttribute.cs 1.程序集 System.Web.Mvc, Version=5.2.3.0, Culture=neutra ...

  9. Mybatis笔记 - 原始Dao开发方法

    使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法.原始Dao的开发方式是基于入门程序的基础上,对 控制程序 进行分层开发,程序员需要 编写 Dao接口 和 ...

  10. ie9 jscript7 内存不足 页面无响应

    花了我差不多一天时间 我是加载一个datagrid ,多表联查,查询几遍(不一定,又是1遍就死了)后 就卡死了...后台日志都是过的.... 后来我发现数据库某个表的数据很多有一模一样的两条,把一份删 ...