之前写过了- 通用池化框架commons-pool2实践以及通用池化框架实践之GenericKeyedObjectPool。接下来我就对这个池化框架进行性能测试。首先呢就是因为这个池化技术必需要有足够的性能,不然通过池化技术优化的部分,在较高QPS的性能测试中,对象池可能成为本机瓶颈。
硬件软件配置
硬件就是我自己的电脑,型号MacBook Pro (16-inch, 2019)
,配置6C16G
。因为这次测试并没有测试到性能极限,在测试方案设计的前阶段已经有了相对明显的结论了。
软件方面,还是Groovy,默认Java进程启动参数。对象池化的设置后面可以在代码中看到,经过我的测试,只要对象池中还有空闲对象就足够满足当前性能。因为我本次用的是固定线程模型,所以换算过来就是对象数大于线程数即可。
测试前准备
测试分成了两部分:无等待归还对象、有等待归还对象。因为无等待归还对象测试过程中,结论出现的太早也太明显了。
可池化对象
/**
* 可池化对象
*/
private static class FunTesterPooled {
String name
int age
}
池化工场
/**
* 池化工厂
*/
private static class FunFactory extends BasePooledObjectFactory<FunTesterPooled> {
@Override
FunTesterPooled create() throws Exception {
return new FunTesterPooled()
}
@Override
PooledObject<FunTesterPooled> wrap(FunTesterPooled obj) {
return new DefaultPooledObject<FunTesterPooled>(obj)
}
@Override
void destroyObject(PooledObject<FunTesterPooled> p, DestroyMode destroyMode) throws Exception {
p.getObject().setName("") //回收资源
super.destroyObject(p, destroyMode)
}
}
对象池
static def initPool() {
def config = new GenericObjectPoolConfig<FunTesterPooled>()
config.setMaxIdle(10)
config.setMinIdle(2)
config.setMaxTotal(thread * 2)
return new GenericObjectPool<FunTesterPooled>(new FunFactory(), config)
}
性能测试用例
static GenericObjectPool<FunTesterPooled> pool
static def desc = "池化框架性能测试"
static int times = 3000000
static int thread = 2
public static void main(String[] args) {
this.pool = initPool()
ThreadBase.COUNT = true
RUNUP_TIME = 0
def barrier = new CyclicBarrier(thread + 1)
POOL_SIZE = thread
def borrows = []
thread.times {
fun {
borrows << pool.borrowObject()
barrier.await()
}
}
barrier.await()
borrows.each {
pool.returnObject(it)
}
output("对象创建完毕 创建数量${pool.getNumIdle()}")
new Concurrent(new FunTester(), thread, desc).start()
pool.close()
}
private static class FunTester extends FixedThread {
FunTester() {
super(null, times, true)
}
@Override
protected void doing() throws Exception {
pool.returnObject(pool.borrowObject())
}
@Override
FunTester clone() {
return new FunTester()
}
}
其中往对象池中添加对象的时候,一开始我思路有点偏,所以想了一个java.util.concurrent.CyclicBarrier
的方案。其实我们直接可以使用官方提供的org.apache.commons.pool2.ObjectPool#addObjects
方法实现,代码非常简单,一行搞定pool.addObjects(thread)
。
测试结果
无等待
线程数 | 执行次数(万) | QPS |
---|---|---|
1 | 300 | 1876172 |
2 | 300 | 1852364 |
5 | 300 | 1533912 |
10 | 300 | 1524538 |
10 | 100 | 1571623 |
20 | 100 | 1568692 |
可以看出,QPS非常高,足够满足线性能测试需求。
等待
使用了休眠2ms的配置。
线程数 | 执行次数(k) | 单线程QPS |
---|---|---|
20 | 10 | 410 |
50 | 10 | 406 |
100 | 5 | 406 |
200 | 2 | 404 |
300 | 2 | 403 |
400 | 2 | 403 |
500 | 2 | 262 |
800 | 2 | 143 |
1000 | 2 | 114 |
看来还是有瓶颈,在并发线程超过400多以后,这个平均单线程QPS会下降会多。猜测可能是org.apache.commons.pool2.impl.LinkedBlockingDeque
和java.util.concurrent.atomic.AtomicLong
两个类的性能瓶颈。可以参考之前做过的Java&Go高性能队列之LinkedBlockingQueue性能测试,虽然不一样可以参考。
虽然后面性能有所下降,瓶颈也在10万以上,也算是满足部分性能测试需求吧。后面我对com.funtest.PoolTest#GenericKeyedObjectPool
对象池的性能,敬请期待。