之前寫過了- 通用池化框架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
對象池的性能,敬請期待。