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