《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记
造成开销的操作包括:1. 线程之间的协调(例如:锁、触发信号以及内存同步等)2. 增加的上下文切换3. 线程的创建和销毁4. 线程的调度一、对性能的思考1 性能与可伸缩性运行速度涉及以下两个指标:某个指定的任务单元需要“多快”才能处理完成、计算资源一定的情况下,能完成“多少”工作。可伸缩性:当增加计算资源时(例如:CPU、内存、存储容器或
·
造成开销的操作包括:
1. 线程之间的协调(例如:锁、触发信号以及内存同步等)
2. 增加的上下文切换
3. 线程的创建和销毁
4. 线程的调度
一、对性能的思考
1 性能与可伸缩性
运行速度涉及以下两个指标:
某个指定的任务单元需要“多快”才能处理完成、计算资源一定的情况下,能完成“多少”工作。
可伸缩性:
当增加计算资源时(例如:CPU、内存、存储容器或I/O带宽),程序的吞吐量或者处理能力能相应地增加。
2 评估各种性能权衡因素
避免不成熟的优化。首先使程序正确,然后再提高运行速度---如果它还运行得不够快。
以测试为基准,不要猜测。
提出问题:例如“更快”的含义是什么?提升多少效率?
二、并发三大定律
Amdahl 定律
Gene Amdahl 发现在计算机体系架构设计过程中,某个部件的优化对整个架构的优化和改善是有上限的。这个发现后来成为知名的 Amdahl 定律。
(即使你有10个老婆,也不能一个月把孩子生下来。)
Gustafson 定律
Gustafson假设随着处理器个数的增加,并行与串行的计算总量也是可以增加的。Gustafson定律认为加速系数几乎跟处理器个数成正比,如果现实情况符合Gustafson定律的假设前提的话,那么软件的性能将可以随着处理个数的增加而增加。
(当你有10个老婆,就会要生更多的孩子。)
Sun-Ni 定律
充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解。
(你要设法让每个老婆都在干活,别让她们闲着。 )
Gene Amdahl 发现在计算机体系架构设计过程中,某个部件的优化对整个架构的优化和改善是有上限的。这个发现后来成为知名的 Amdahl 定律。
(即使你有10个老婆,也不能一个月把孩子生下来。)
Gustafson 定律
Gustafson假设随着处理器个数的增加,并行与串行的计算总量也是可以增加的。Gustafson定律认为加速系数几乎跟处理器个数成正比,如果现实情况符合Gustafson定律的假设前提的话,那么软件的性能将可以随着处理个数的增加而增加。
(当你有10个老婆,就会要生更多的孩子。)
Sun-Ni 定律
充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解。
(你要设法让每个老婆都在干活,别让她们闲着。 )
1 示例:在各种框架中隐藏的串行部分
2 Amdahl定律的应用
2 Amdahl定律的应用
多线程中串行部分是性能提升的瓶颈,例如:
多线程在同一个队列中取出任务,因为需要保证线程安全肯定在队列上加锁,此时就是多线程中串行部分。
多线程通常是处理一些计算,而计算结果可能需要多个线程间进行共享,这也是多线程中串行部分。
三、线程引入的开销
1 上下文切换
大多数通用的处理器中,上下文切换的开销相当于5000~10000个时钟周期(几微妙)
UNIX系统的vmstat、mpstat命令和Windows系统的perfmon工具都能报告上下文切换次数以及和内核中执行时间所占比例等信息。
2 内存同步
在 synchronized 和 volatile 提供的可见性保证中可能会使用一些特殊指令,即内存栅栏(Memory Barrier)。
public
String getStoogeNames(){
Vector<String> stooges =
new
Vector<String>();
stooges.add(
"Moe"
);
stooges.add(
"Larry"
);
stooges.add(
"Curly"
);
return
stooges.toString();
}
在执行getStoogeNames中,至少将Vector上的锁获取/释放4此,3次add操作与1次toString操作。
JVM会把在一起操作进行合并,可能仅需要获取1次add锁与1次toString锁。
3 阻塞
四、减少锁的竞争
在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁。
有3中方式可以降低锁的竞争程度:
1. 减少锁的持有时间。
2. 降低锁的请求频率。
3. 使用带有协调机制的独占锁,这些机制允许更高的并发性。
有3中方式可以降低锁的竞争程度:
1. 减少锁的持有时间。
2. 降低锁的请求频率。
3. 使用带有协调机制的独占锁,这些机制允许更高的并发性。
1 缩小锁的范围(“快进快出”)
如果一个方法中,仅有一个变量是需要多线程间共享的,不需要在方法上添加synchronized,因为这样会直接锁住整个方法导致其多线程间穿行执行,可以通过方法中仅锁住对共享变量操作的部分来缩小锁的范围提高性能。
2 减小锁的粒度(锁分解)
在一个分装中,如果分别提供的多个方法是分别对多个数据源操作,最严谨的方式是在所有方法上都把当前类作为锁定条件,但是可以通过在每个数据源上添加一个独立的锁。
3 锁分段
上一个中是一个类中涉及到多个数据源,如果仅有一个数据源(例如:Map)如何提高性能?
在数据源上添加分段锁,例如把map的个数除以4,4份中每一份是用一个单独的锁来锁定。
4 避免热点域
前面提到的通常是针对一个变量、一个数据集合、多个数据集合提高性能的办法,但是有些情况下一个方法内涉及到多个变量或者同一个变量的多个操作,可以通过减少这种情况出现的次数提升性能。
5 一些替代独占锁的方法
通过放弃独占锁来提升性能。如并发容器,ReadWriteLock,不可变对象以及原子变量。
6 监测CPU的利用率
CPU没有得到充分利用的原因:
. 负载不充足
. I/O密集
. 外部限制
. 锁竞争
. 负载不充足
. I/O密集
. 外部限制
. 锁竞争
7 向对象池说“不”
对象分配操作的开销比同步的开销更低。
五、示例:比较Map的性能
六、减少上下文切换的开销
参考资料:
并发三大定律 摘录自《温绍锦 - Java并发程序设计教程》
更多推荐
已为社区贡献1条内容
所有评论(0)