自从我写任何东西以来已经有一段时间了,我一直在忙于我的新工作,该工作涉及在性能调整方面做一些有趣的工作。 挑战之一是减少应用程序关键部分的对象创建。
尽管Java随着时间的推移改进了GC算法,但垃圾回收打h一直是Java的主要难题。 Azul是开发无暂停GC的市场领导者,但Azul JVM并非免费提供!
创建过多的临时/垃圾对象并不能很好地工作,因为它会为GC创建工作,并且会对延迟产生负面影响。 过多的垃圾也无法在多核系统上正常工作,因为它会导致缓存污染。
那么我们应该如何解决呢?
垃圾少编码
仅当您知道需要多少对象并预先分配它们时,这才有可能,但是实际上很难找到。 但是,即使您成功做到了,也必须担心另一个问题
- 您可能没有足够的内存来容纳所需的所有对象
- 您还必须处理并发
那么上述问题的解决方案是什么
有一种对象池设计模式可以解决以上两个问题。 它使您可以指定池中所需的许多对象,并处理并发请求以服务所请求的对象。
对象池一直是许多具有低延迟要求的应用程序的基础。 Flyweight设计模式是对象池的一种风格。
上面的两种模式都将帮助我们避免创建对象。 太好了,因此现在减少了GC工作,并且理论上我们的应用程序性能应该得到改善。 实际上,不会那样做,因为对象池/ Flyweight必须处理并发性,并且由于并发性问题而失去了避免对象创建的任何优势。
处理并发的最常见方法是什么
对象池是一个典型的生产者/消费者问题,可以使用以下技术来解决:
同步:这是在JDK 1.5之前处理并发的唯一方法。 Apache编写了一个基于同步的出色的对象池 API
锁: Java在JDK 1.5之后增加了对并发编程的出色支持。 已经进行了一些使用锁来开发对象池的工作,例如: furious-objectpool
无锁:我找不到使用完全无锁技术构建的任何实现,但是furious-objectpool使用ArrayBlocking队列和ConcurrentLinked队列的混合
衡量绩效
在此测试中,我创建了一个包含100万个对象的池,并且这些对象由不同的池实现访问,这些对象从池中取出并返回到池中。
此测试首先从1个线程开始,然后增加线程数以衡量不同池实现在竞争中的执行情况
- X轴–螺纹数
- Y轴–以毫秒为单位的时间–越短的时间越好
该测试包括来自Apache的池,Furious池和基于ArrayBlocking的池
Apache的性能最差,并且随着线程数量的增加,性能会进一步下降。 原因是Apache池基于大量使用“同步”
其他两个(基于Furious和ArrayBlocking的池)的性能更好,但是随着争用的增加,它们两者的速度也会降低。
当12个线程试图访问该池时,基于ArrayBlocking队列的池对于100万个项目大约需要1000毫秒。 内部使用Arrayblocking队列的愤怒池大约需要1975 ms。
我必须进行更详细的调查,以找出为什么Furious花费双倍的时间,因为它也是基于ArrayBlocking队列的。
数组阻塞队列的性能不错,但这是一种基于锁定的方法。 如果可以实现无锁池,我们将获得哪种类型的性能?
锁免费游泳池
实现无锁池不是不可能的,但是有点困难,因为您必须处理多个生产者和消费者。
我将实现一个混合池,该池将在生产者端使用锁定,而在消费者端使用非阻塞技术。
让我们看一些数字
我使用新的实现(FastPool)进行了相同的测试,它比ArayBlocking队列快了30%。
30%的改善还不错,它绝对可以帮助我们实现延迟目标。
是什么使快速池快速!
我使用了两种技术来使其快速运行
- 生产者是基于锁的–使用锁来管理多个生产者,这与“数组阻止”队列相同,因此没什么大不了的。
- 立即发布已发布项目–在使用便宜的内存屏障释放锁之前,它会发布元素。 这会有所收获
- 消费者不受阻碍–使用CAS来实现这一目标,消费者永远不会因生产者而受到阻碍。 数组阻止队列阻止了使用者,因为它对生产者和使用者使用相同的锁
- 线程局部以保持值的局部性–线程局部用于获取最后使用的值,这在很大程度上减少了争用。
如果您有兴趣查看代码,则可以使用@ FastObjectPool.java
翻译自: https://www.javacodegeeks.com/2013/07/lock-less-java-object-pool.html