ConcurrentHashMap性能測試

之前在測試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個keyvalue,然後通過多線程隨機從這些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 !

閱讀原文,跳轉我的倉庫地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章