之前在測試commons-pool2
相關實現的時候,發現在線程接近500時候,性能瓶頸降低非常厲害,就好像碰到了總體性能的天花板一樣,隨着線程繼續增加而單線程性能急速下降的現象。當時粗略判斷其中一個原因是用來存儲對象映射關係的java.util.concurrent.ConcurrentHashMap
存在瓶頸導致。
所以今天我特意來測試一下java.util.concurrent.ConcurrentHashMap
的查詢性能,其他增改的功能暫時不做測試了。關於另外一個可能的原因java.util.concurrent.atomic.AtomicLong
,我們下期再測。有興趣的可以先看看我之前對於更強大的多線程計數器java.util.concurrent.atomic.LongAdder
的性能測試:性能測試中的LongAdder。下面是之前遇到兩種不同類型的對象池的性能測試文章:通用池化框架GenericObjectPool性能測試、通用池化框架GenericKeyedObjectPool性能測試。
測試方案
先說一下思路和場景設計。思路還是沿用之前的性能測試,通過固定線程的性能模型進行測試,通過調整次數和線程數來測試java.util.concurrent.ConcurrentHashMap
的性能表現。場景設計上我先把java.util.concurrent.ConcurrentHashMap
添加N個key
和value
,然後通過多線程隨機從這些key
裏面取值。
這樣本地測試就有了三個變量線程數
、次數
、key
的數量,本次重點放在了200線程以上的性能表現。
PS:硬件和軟件配置參考以前的文章,這裏就不多說了。
測試用例
照例方案依舊使用FunTester
性能測試框架提供的能力,採取Groovy
腳本實現。相信有一定Java基礎的同學閱讀起來是沒有問題的。
package com.funtest.groovytest
import com.funtester.base.constaint.FixedThread
import com.funtester.base.constaint.ThreadBase
import com.funtester.frame.SourceCode
import com.funtester.frame.execute.Concurrent
import java.util.concurrent.ConcurrentHashMap
class ConcurrentHashMapTest extends SourceCode {
static ConcurrentHashMap<Integer, Integer> maps = new ConcurrentHashMap<>()
static int times = 1_0000
static int threads = 200
static int num = 100
static def desc = "ConcurrentHashMap性能測試"
public static void main(String[] args) {
1.upto(num) {
maps.put(it, it)
}
ThreadBase.COUNT = false
RUNUP_TIME = 0
new Concurrent(new FunTester(), threads, desc).start()
}
private static class FunTester extends FixedThread {
FunTester() {
super(null, times, true)
}
@Override
protected void doing() throws Exception {
maps.get(getRandomInt(num))
}
@Override
FunTester clone() {
return new FunTester()
}
}
}
測試結果
由於測試中基本都觸碰到硬件(CPU)瓶頸,所以本次也就不記錄CPU使用率了,相當於都是在CPU資源有限情況下的性能測試數據,其實測試中發現次數影響也不大。
線程數 | 次數(千) | key數量 | 單線程QPS |
---|---|---|---|
200 | 10 | 100 | 3038 |
200 | 20 | 100 | 3539 |
200 | 40 | 100 | 4066 |
200 | 80 | 100 | 4334 |
200 | 10 | 200 | 2823 |
200 | 20 | 200 | 3587 |
200 | 40 | 200 | 4736 |
200 | 10 | 400 | 2919 |
200 | 10 | 50 | 2873 |
200 | 10 | 20 | 3218 |
200 | 10 | 1000 | 3256 |
300 | 10 | 100 | 1893 |
300 | 20 | 100 | 2514 |
300 | 40 | 100 | 3214 |
300 | 20 | 300 | 1798 |
300 | 20 | 500 | 2832 |
500 | 20 | 100 | 1722 |
500 | 20 | 1000 | 1509 |
1000 | 20 | 1000 | 816 |
1000 | 10 | 100 | 724 |
測試到此,結論比較明顯了,影響java.util.concurrent.ConcurrentHashMap
的主要因素還是機器CPU資源不夠用了。對於相同的資源情況下,線程數更低自然獲得更強的單線程性能,如果增加線程確實可以獲取更大的總體QPS。在key
值方面,值越多,QPS越低。在測試次數上,自然是字數越多,QPS也大,也符合之前多次測試中的結論。
但是當我重新檢查代碼的時候卻發現一個問題,在com.funtest.groovytest.ConcurrentHashMapTest.FunTester#doing
方法中其實還有一段耗時的請求,就是com.funtester.frame.SourceCode#getRandomInt
,經過我重新測試,發現java.util.concurrent.ConcurrentHashMap
的性能得到了十幾倍的提升。
不得不說我大意了,本期文章標題應當修改爲java.util.concurrent.ThreadLocalRandom
性能測試。
一下是com.funtester.frame.SourceCode#getRandomInt
的內容:
/**
* 獲取隨機數,獲取1~num 的數字,包含 num
*
* @param num 隨機數上限
* @return 隨機數
*/
public static int getRandomInt(int num) {
return ThreadLocalRandom.current().nextInt(num) + 1;
}
我依此法重新測試了java.util.concurrent.atomic.AtomicLong
,發現也是QPS超高,排除了我之前的想法。看來commons-pool2
的瓶頸不在這兩個地方。以後等我仔細再研究研究,有結論再跟大家分享。
Have Fun ~ Tester !
- 性能測試專題
- Java、Groovy、Go、Python
- FunTester社羣風采
- 測試理論雞湯
- 接口功能測試專題
- FunTester視頻專題
- 案例分享:方案、BUG、爬蟲
- UI自動化專題
- 測試工具專題
閱讀原文,跳轉我的倉庫地址