ConcurrentHashMap性能测试

2022-07-08 15:20:49 浏览数 (1)

之前在测试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基础的同学阅读起来是没有问题的。

代码语言:javascript复制

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的内容:

代码语言:javascript复制
    /**
     * 获取随机数,获取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 !

0 人点赞