使用线程的一种说法是为了提高性能。多线程可以使程序充分利用闲置的资源,提高资源的利用率,同时能够并行处理任务,提高系统的响应性。 但是很显然,引入线程的同时也引入了系统的复杂性。另外系统的性能并不是总是随着线程数的增加而总是提高。
性能与伸缩性
性能的提升通常意味着可以用更少的资源做更多的事情。这里资源是包括我们常说的cpu周期、内存、网络带宽、磁盘io、数据库、web服务等等。 引入多线程可以充分利用多核的优势,充分利用io阻塞带来的延迟,也可以降低网络开销带来的影响,从而提高单位时间内的响应效率。
为了提高性能,需要有效的利用我们现有的处理资源,同时也要开拓新的可用资源。例如,对于cpu而言,理想状况下希望cpu能够满负荷工作。当然这里满负荷工作是指做有用的事情,而不是无谓的死循环或者等待。受限于cpu的计算能力,如果cpu达到了极限,那么很显然我们充分利用了计算能力。对于io而言(内存、磁盘、网络等),如果达到了其对于的带宽,这些资源的利用率也就上去了。理想状况下所有资源的能力都被用完了,那么这个系统的性能达到了最大值。
为了衡量系统的性能,有一些指标用于定性、定量的分析。例如服务时间、等待时间、吞吐量、效率、可伸缩性、生成量等等。服务时间、等待时间等用于衡量系统的效率,即到底有多快。吞吐量、生成量等用于衡量系统的容量,即能够处理多少数据。除此之外,有效服务时间、中断时间等用于能力系统的可靠性和稳定性等。
可伸缩性的意思是指增加计算资源,吞吐量和生产量相应得到的改进。 从算法的角度讲,通常用复杂度来衡量其对应的性能。例如时间复杂度、空间复杂度等。
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:
- gustafson's law:
- sun-ni law:
- 多核系统中三种典型锁竞争的加速比分析
- 阿姆达尔定律和gustafson定律的等价性
©2009-2014 imxylz
|求贤若渴