深入浅出 Java Concurrency (40): 并发总结 part 4 性能与伸缩性[转]
性能与伸缩性
使用线程的一种说法是为了提高性能。多线程可以使程序充分利用闲置的资源,提高资源的利用率,同时能够并行处理任务,提高系统的响应性。 但是很显然,引入线程的同时也引入了系统的复杂性。另外系统的性能并不是总是随着线程数的增加而总是提高。
性能与伸缩性
性能的提升通常意味着可以用更少的资源做更多的事情。这里资源是包括我们常说的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切片浪费,而操作系统挂起则会导致更多的上下文切换。
了解了性能的提升的几个方面,也了解性能的开销后,应用程序就要根据实际的场景进行取舍和评估。没有一劳永逸的优化方案,不断的进行小范围改进和调整是提高性能的有效手段。当前一些大的架构调整也会导致较大的性能的提升。
简单的原则是在保证逻辑正确的情况小,找到性能瓶颈,小步改进和优化。
参考资料
- Amdahl's law: http://en.wikipedia.org/wiki/Amdahl%27s_law
- Gustafson's law: http://en.wikipedia.org/wiki/Gustafson%27s_law
- Sun-Ni law: http://en.wikipedia.org/wiki/Sun-Ni_law
- 多核系统中三种典型锁竞争的加速比分析 http://blog.csdn.net/drzhouweiming/article/details/1800319
- 阿姆达尔定律和Gustafson定律的等价性 http://book.51cto.com/art/201004/197506.htm
深入浅出 Java Concurrency (40): 并发总结 part 4 性能与伸缩性[转]的更多相关文章
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
(转自:http://blog.csdn.net/fg2006/article/details/6404226) 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器 ...
- 深入浅出 Java Concurrency (21): 并发容器 part 6 可阻塞的BlockingQueue (1)[转]
在<并发容器 part 4 并发队列与Queue简介>节中的类图中可以看到,对于Queue来说,BlockingQueue是主要的线程安全版本.这是一个可阻塞的版本,也就是允许添加/删除元 ...
- 深入浅出 Java Concurrency (16): 并发容器 part 1 ConcurrentMap (1)[转]
从这一节开始正式进入并发容器的部分,来看看JDK 6带来了哪些并发容器. 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器,Collections.synchro ...
- 深入浅出 Java Concurrency (27): 并发容器 part 12 线程安全的List/Set[转]
本小节是<并发容器>的最后一部分,这一个小节描述的是针对List/Set接口的一个线程版本. 在<并发队列与Queue简介>中介绍了并发容器的一个概括,主要描述的是Queue的 ...
- 深入浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque[转]
这个小节介绍Queue的最后一个工具,也是最强大的一个工具.从名称上就可以看到此工具的特点:双向并发阻塞队列.所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条 ...
- 深入浅出 Java Concurrency (17): 并发容器 part 2 ConcurrentMap (2)[转]
本来想比较全面和深入的谈谈ConcurrentHashMap的,发现网上有很多对HashMap和ConcurrentHashMap分析的文章,因此本小节尽可能的分析其中的细节,少一点理论的东西,多谈谈 ...
- 深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱
常见的并发陷阱 volatile volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的.参考指令重排序 volatile最常见于下面两种场景. a. 循环 ...
- 深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]
常见的并发场景 线程池 并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量. 最常见.比较复杂一个场景是Web容器的线程池.Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复 ...
- 深入浅出 Java Concurrency (37): 并发总结 part 1 死锁与活跃度[转]
死锁与活跃度 前面谈了很多并发的特性和工具,但是大部分都是和锁有关的.我们使用锁来保证线程安全,但是这也会引起一些问题. 锁顺序死锁(lock-ordering deadlock):多个线程试图通 ...
随机推荐
- Android笔记之调用系统相机拍照
参考链接: 拍照 | Android Developers, Android相机拍照方向旋转的解决方案:ExifInterface - 简书 Demo链接:https://pan.baidu.co ...
- Thread-per-Message 这个工作交给你了
Per是“每一”的意思,所以thread per message解释过来就是“每个消息一个线程”,message在这里可以看做是“命令”或“请求”的意思,对每隔命令或请求,分配一个线程,有这个线程执行 ...
- 8.spark Core 进阶1
(e.g. standalone manager, Mesos, YARN) In "cluster" mode, the framework launches the ...
- Leetcode - K Sum
List<List<Integer>> kSum_Trim(int[] a, int target, int k) { List<List<Integer>& ...
- Java 基础 - 装箱, 拆箱
总结 1-装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的.(xxx代表对应的基本数据类型).例如:在装箱的时候自动调用的是Integer的 ...
- 【Servlet】Servlet监听器
一.Servlet监听器的概念 Servlet监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext.HttpSession和ServletRequest等域对象的创建与销 ...
- 巧用CSS3的calc()宽度计算做响应模式布局
今天浏览这个http://www.sitepoint.com站时,因为好奇看了下人家写的代码,结果发现了这行代码, 于是就研究了一下,calc()从字面我们可以把他理解为一个函数function.其实 ...
- 0919CSP-S模拟测试赛后总结
60分-rank36,不出所料又炸了. 总是试图稳住成绩,但就是不能避免这样的大起伏.这样不行啊. T1可是道sb题啊……对着题干yy了一个多小时,正解基本都想到了,只差一个结构体排序. 然而即使我真 ...
- MFC打开/保存文件对话框:CFileDialog
MFC打开/保存文件对话框:CFileDialog CFileDialog 文件选择对话框的使用:首先构造一个对象并提供相应的参数,构造函数原型如下: CFileDialog::CFileDial ...
- POJ 2104:K-th Number 整体二分
感觉整体二分是个很有趣的东西. 在别人的博客上看到一句话 对于二分能够解决的询问,如果有多个,那么如果支持离线处理的话,那么就可以使用整体二分了 树套树写了一天还是WA着,调得焦头烂额,所以决定学cd ...