Java基准测试工具JMH高级使用

简介: Java基准测试工具JMH高级使用

去年,我们写过一篇关于JMH的入门使用的文章:Java基准测试工具JMH使用,今天我们再来聊一下关于JMH的高阶使用。主要我们会围绕着以下几点来讲:

  • 对称并发测试
  • 非对称并发测试
  • 阻塞并发测试
  • Map并发测试

关键词

@State 在很多时候我们需要维护一些状态内容,比如在多线程的时候我们会维护一个共享的状态,这个状态值可能会在每根线程中都一样,也有可能是每根线程都有自己的状态,JMH为我们提供了状态的支持。该注解只能用来标注在类上,因为类作为一个属性的载体。@State的状态值主要有以下几种:

Scope.Benchmark 该状态的意思是会在所有的Benchmark的工作线程中共享变量内容。

Scope.Group 同一个Group的线程可以享有同样的变量

Scope.Thread 每个线程都享有一份变量的副本,线程之间对于变量的修改不会相互影响

@Group 执行组的识别号

@GroupThreads 执行某个方法所需要的线程数量

对称并发测试

我们编写的所有基准测试都会被JMH框架根据方法名的字典顺序排序之后串行执行,然而有些时候我们会想要对某个类的读写方法并行执行,比如,我们想要在修改某个原子变量的时候又有其他线程对其进行读取操作。

@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Group)
public class SymmetricBenchmark {
    private AtomicLong counter;
    @Setup
    public void init() {
        this.counter = new AtomicLong();
    }
    @GroupThreads(5)
    @Group("atomic")
    @Benchmark
    public void inc() {
        this.counter.incrementAndGet();
    }
    @GroupThreads(5)
    @Group("atomic")
    @Benchmark
    public long get() {
        return this.counter.get();
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(SymmetricBenchmark.class.getSimpleName())
                .build();
        new Runner(opt).run();
    }
}

结果为:

Benchmark                      Mode  Cnt  Score   Error  Units
SymmetricBenchmark.atomic      avgt    5  0.126 ± 0.009  us/op
SymmetricBenchmark.atomic:get  avgt    5  0.062 ± 0.011  us/op
SymmetricBenchmark.atomic:inc  avgt    5  0.190 ± 0.011  us/op

我们在对AtomicLong进行自增操作的同时又会对其进行读取操作,这就是我们经常见到的高并发环境中某些API的操作方式,同样也是线程安全存在隐患的地方。5个线程对AtomicLong执行自增操作,5个线程对AtomicLong执行读取时的性能输出说明如下:

  • group atomic(5个读线程,5个写线程)的平均响应时间为0.126 us,误差为0.009。
  • group atomic(5个读线程)同时读取AtomicLong变量的速度为0.062 us,误差为0.011。
  • group atomic(5个写线程)同时修改AtomicLong变量的速度为0.190 us,误差为0.011 。
非对称并发测试

有时,您需要达到非对称测试的目的。

@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Group)
public class AsymmetricBenchMark {
    private AtomicLong counter;
    @Setup
    public void up() {
        counter = new AtomicLong();
    }
    @Benchmark
    @Group("atomic")
    @GroupThreads(3)
    public long inc() {
        return counter.incrementAndGet();
    }
    @Benchmark
    @Group("atomic")
    @GroupThreads(1)
    public long get() {
        return counter.get();
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(AsymmetricBenchMark.class.getSimpleName())
                .build();
        new Runner(opt).run();
    }
}

结果为:

Benchmark                       Mode  Cnt  Score   Error  Units
AsymmetricBenchMark.atomic      avgt    5  0.053 ± 0.003  us/op
AsymmetricBenchMark.atomic:get  avgt    5  0.025 ± 0.006  us/op
AsymmetricBenchMark.atomic:inc  avgt    5  0.062 ± 0.005  us/op

我们在对AtomicLong进行自增操作的同时又会对其进行读取操作,这就是我们经常见到的高并发环境中某些API的操作方式,同样也是线程安全存在隐患的地方。3个线程对AtomicLong执行自增操作,1个线程对AtomicLong执行读取时的性能输出说明如下:

  • group atomic(1个读线程,3个写线程)的平均响应时间为0.053 us,误差为0.003 。
  • group atomic(1个读线程)同时读取AtomicLong变量的速度为0.025 us,误差为0.006 。
  • group atomic(3个写线程)同时修改AtomicLong变量的速度为0.062 us,误差为0.005 。
阻塞并发测试

有些时候我们想要执行某些容器的读写操作时可能会引起阻塞,比如blockqueue,在某些情况下程序会出现长时间的阻塞,这就严重影响了我们测试的结果,我们可以通过设置Options的timeout来强制让每一个批次的度量超时,超时的基准测试数据将不会被纳入统计之中。

以下测试,我们设置每批次如果超过10秒,就被认为超时不计入统计。

@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Group)
public class InterruptBenchmark {
    private BlockingQueue<Integer> queue;
    private final static int VALUE = Integer.MAX_VALUE;
    @Setup
    public void init() {
        this.queue = new ArrayBlockingQueue<>(10);
    }
    @GroupThreads(5)
    @Group("queue")
    @Benchmark
    public void put()
            throws InterruptedException {
        this.queue.put(VALUE);
    }
    @GroupThreads(5)
    @Group("queue")
    @Benchmark
    public int take()
            throws InterruptedException {
        return this.queue.take();
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(InterruptBenchmark.class.getSimpleName())
                // 将每个批次的超时时间设置为10秒
                .timeout(TimeValue.milliseconds(10000))
                .build();
        new Runner(opt).run();
    }
}

结果为:

Benchmark                      Mode  Cnt      Score       Error  Units
InterruptBenchmark.queue       avgt    5  19204.384 ± 23024.739  ns/op
InterruptBenchmark.queue:put   avgt    5  14049.887 ± 49670.027  ns/op
InterruptBenchmark.queue:take  avgt    5  24358.880 ± 31679.280  ns/op

有些执行时被阻塞的结果就被忽略了,报告中会如下所示:

Iteration   5: (benchmark timed out, interrupted 1 times) 27130.727 ±(99.9%) 53300.757 ns/op

如果超时时间设置得过小,那么,会得到如下警告:

# Timeout: 1000 ms per iteration, ***WARNING: The timeout might be too low!***
Map并发测试

对比几大线程安全Map的多线程下的读写性能,以后类似的操作可以按照这个模板来。

@Fork(1)
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Group)
public class MapBenchMark {
    @Param({"ConcurrentHashMap", "ConcurrentSkipListMap", "Hashtable", "Collections.synchronizedMap"})
    private String type;
    private Map<Integer, Integer> map;
    @Setup
    public void setUp() {
        switch (type) {
            case "ConcurrentHashMap":
                this.map = new ConcurrentHashMap<>();
                break;
            case "ConcurrentSkipListMap":
                this.map = new ConcurrentSkipListMap<>();
                break;
            case "Hashtable":
                this.map = new Hashtable<>();
                break;
            case "Collections.synchronizedMap":
                this.map = Collections.synchronizedMap(
                        new HashMap<>());
                break;
            default:
                throw new IllegalArgumentException("Illegal map type.");
        }
    }
    @Group("map")
    @GroupThreads(5)
    @Benchmark
    public void putMap() {
        int random = randomIntValue();
        this.map.put(random, random);
    }
    @Group("map")
    @GroupThreads(5)
    @Benchmark
    public Integer getMap() {
        return this.map.get(randomIntValue());
    }
    /**
     * 计算一个随机值用作Map中的Key和Value
     *
     * @return
     */
    private int randomIntValue() {
        return (int) Math.ceil(Math.random() * 600000);
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(MapBenchMark.class.getSimpleName())
                .build();
        new Runner(opt).run();
    }
}

结果如下:

Benchmark                                     (type)   Mode  Cnt        Score        Error  Units
MapBenchMark.map                   ConcurrentHashMap  thrpt    5  4903943.211 ± 208719.270  ops/s
MapBenchMark.map:getMap            ConcurrentHashMap  thrpt    5  2442687.631 ± 251150.685  ops/s
MapBenchMark.map:putMap            ConcurrentHashMap  thrpt    5  2461255.580 ± 260557.472  ops/s
MapBenchMark.map               ConcurrentSkipListMap  thrpt    5  3471371.602 ± 334184.434  ops/s
MapBenchMark.map:getMap        ConcurrentSkipListMap  thrpt    5  1710540.889 ± 196183.472  ops/s
MapBenchMark.map:putMap        ConcurrentSkipListMap  thrpt    5  1760830.713 ± 263480.175  ops/s
MapBenchMark.map                           Hashtable  thrpt    5  1966883.854 ± 197740.289  ops/s
MapBenchMark.map:getMap                    Hashtable  thrpt    5   676801.687 ±  71672.436  ops/s
MapBenchMark.map:putMap                    Hashtable  thrpt    5  1290082.167 ± 174730.435  ops/s
MapBenchMark.map         Collections.synchronizedMap  thrpt    5  1976316.282 ±  99878.457  ops/s
MapBenchMark.map:getMap  Collections.synchronizedMap  thrpt    5   655744.125 ±  73634.788  ops/s
MapBenchMark.map:putMap  Collections.synchronizedMap  thrpt    5  1320572.158 ±  75428.848  ops/s

我们可以看到,在 putMap 和 getMap 方法中,通过随机值的方式将取值作为 key 和 value 存入 map 中,同样也是通过随机值的方式将取值作为 key 从 map 中进行数据读取(当然读取的值可能并不存在)。还有我们在基准方法中进行了随机值的运算,虽然随机值计算所耗费的CPU时间也会被纳入基准结果的统计中,但是每一个 map 都进行了相关的计算,因此,我们可以认为大家还是站在了同样的起跑线上,故而可以对其忽略不计。

基准测试的数据可以表明,在5个线程同时进行 map 写操作,5个线程同时进行读操作时,参数 type=ConcurrentHashMap 的性能是最佳的 。

下一篇,将和大家介绍下JMH的profiler

目录
相关文章
|
1月前
|
监控 Java 测试技术
Java开发现在比较缺少什么工具?
【10月更文挑战第15天】Java开发现在比较缺少什么工具?
36 1
|
10天前
|
安全 前端开发 测试技术
如何选择合适的自动化安全测试工具
选择合适的自动化安全测试工具需考虑多个因素,包括项目需求、测试目标、系统类型和技术栈,工具的功能特性、市场评价、成本和许可,以及集成性、误报率、社区支持、易用性和安全性。综合评估这些因素,可确保所选工具满足项目需求和团队能力。
|
9天前
|
监控 网络协议 Java
一些适合性能测试脚本编写和维护的工具
一些适合性能测试脚本编写和维护的工具
|
10天前
|
安全 网络协议 关系型数据库
最好用的17个渗透测试工具
渗透测试是安全人员为防止恶意黑客利用系统漏洞而进行的操作。本文介绍了17款业内常用的渗透测试工具,涵盖网络发现、无线评估、Web应用测试、SQL注入等多个领域,包括Nmap、Aircrack-ng、Burp Suite、OWASP ZAP等,既有免费开源工具,也有付费专业软件,适用于不同需求的安全专家。
14 2
|
20天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
44 2
|
21天前
|
Web App开发 定位技术 iOS开发
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
20 1
|
24天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
24天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
27天前
|
Web App开发 Java
使用java操作浏览器的工具selenium-java和webdriver下载地址
【10月更文挑战第12天】Selenium-java依赖包用于自动化Web测试,版本为3.141.59。ChromeDriver和EdgeDriver分别用于控制Chrome和Edge浏览器,需确保版本与浏览器匹配。示例代码展示了如何使用Selenium-java模拟登录CSDN,包括设置驱动路径、添加Cookies和获取页面源码。
|
1月前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
37 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!