之前在測試commons-pool2相關實現(xiàn)的時候,發(fā)現(xiàn)在線程接近500時候,性能瓶頸降低非常厲害,就好像碰到了總體性能的天花板一樣,隨著線程繼續(xù)增加而單線程性能急速下降的現(xiàn)象。當時粗略判斷其中一個原因是用來存儲對象映射關系的java.util.concurrent.ConcurrentHashMap存在瓶頸導致。
所以今天我特意來測試一下java.util.concurrent.ConcurrentHashMap的查詢性能,其他增改的功能暫時不做測試了。關于另外一個可能的原因java.util.concurrent.atomic.AtomicLong,我們下期再測。有興趣的可以先看看我之前對于更強大的多線程計數(shù)器java.util.concurrent.atomic.LongAdder的性能測試:性能測試中的LongAdder。下面是之前遇到兩種不同類型的對象池的性能測試文章:通用池化框架GenericObjectPool性能測試、通用池化框架GenericKeyedObjectPool性能測試。
測試方案
先說一下思路和場景設計。思路還是沿用之前的性能測試,通過固定線程的性能模型進行測試,通過調(diào)整次數(shù)和線程數(shù)來測試java.util.concurrent.ConcurrentHashMap的性能表現(xiàn)。場景設計上我先把java.util.concurrent.ConcurrentHashMap添加N個key和value,然后通過多線程隨機從這些key里面取值。
這樣本地測試就有了三個變量線程數(shù)、次數(shù)、key的數(shù)量,本次重點放在了200線程以上的性能表現(xiàn)。
PS:硬件和軟件配置參考以前的文章,這里就不多說了。
測試用例
照例方案依舊使用FunTester性能測試框架提供的能力,采取Groovy腳本實現(xiàn)。相信有一定Java基礎的同學閱讀起來是沒有問題的。
package com.funtest.groovytestimport com.funtester.base.constaint.FixedThreadimport com.funtester.base.constaint.ThreadBaseimport com.funtester.frame.SourceCodeimport com.funtester.frame.execute.Concurrentimport java.util.concurrent.ConcurrentHashMapclass ConcurrentHashMapTest extends SourceCode { static ConcurrentHashMap 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資源有限情況下的性能測試數(shù)據(jù),其實測試中發(fā)現(xiàn)次數(shù)影響也不大。
線程數(shù) 次數(shù)(千) key數(shù)量 單線程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資源不夠用了。對于相同的資源情況下,線程數(shù)更低自然獲得更強的單線程性能,如果增加線程確實可以獲取更大的總體QPS。在key值方面,值越多,QPS越低。在測試次數(shù)上,自然是字數(shù)越多,QPS也大,也符合之前多次測試中的結論。
但是當我重新檢查代碼的時候卻發(fā)現(xiàn)一個問題,在com.funtest.groovytest.ConcurrentHashMapTest.FunTester#doing方法中其實還有一段耗時的請求,就是com.funtester.frame.SourceCode#getRandomInt,經(jīng)過我重新測試,發(fā)現(xiàn)java.util.concurrent.ConcurrentHashMap的性能得到了十幾倍的提升。
不得不說我大意了,本期文章標題應當修改為java.util.concurrent.ThreadLocalRandom性能測試。
一下是com.funtester.frame.SourceCode#getRandomInt的內(nèi)容:
/** * 獲取隨機數(shù),獲取1~num 的數(shù)字,包含 num * * @param num 隨機數(shù)上限 * @return 隨機數(shù) */ public static int getRandomInt(int num) { return ThreadLocalRandom.current().nextInt(num) + 1; }
我依此法重新測試了java.util.concurrent.atomic.AtomicLong,發(fā)現(xiàn)也是QPS超高,排除了我之前的想法??磥韈ommons-pool2的瓶頸不在這兩個地方。以后等我仔細再研究研究,有結論再跟大家分享。