本文是对作者上一篇文章中 Java 面试题之 Logback 打印日志是如何获取当时办法名称的? 介绍的四种获取当时履行办法名称计划的基准测验报告。这四种办法如下,

  1. 运用 Thread.currentThread().getStackTrace() 计划
  2. 运用反常目标的 getStackTrace() 计划
  3. 运用匿名内部类的 getClass().getEnclosingMethod() 计划
  4. Java 9 的 Stack-Walking API 计划

本文将经过运用专业基准测验东西 JMH 来对如上四种计划进行测验。

基准测验,也称之为功能测验,是一种用于衡量核算机体系,软件应用或硬件组件功能的测验办法。基准测验旨在经过运转一系列标准化的任务场景来丈量体系的功能体现,从而帮助评估体系的各种目标,如响应时刻、吞吐量、延迟、资源利用率等。

JMH,即 Java Microbenchmark Harness,是专门用于代码微基准测验的东西套件。何谓 Micro Benchmark 呢?简略的来说就是基于办法层面的基准测验,精度能够达到微秒级。其由 Oracle/openjdk 内部开发 JIT 编译器的大佬们所开发,作为 Java 的办法级功能测验东西能够说是十分合适的。

测验环境是作者 2018 年购买的笔记本,装备如下,

关于四种获取当时履行办法名称计划的基准测验报告

重点看:

  • cpu:i7-8759H,6 核 12 线程
  • 内存 16 GB(2667 MHZ)

前置准备

本文不打当作基准测验东西 JMH 的具体科普文章,有爱好的我们自行百度 JMH 运用。所以我在这儿只给我们解说 JMH 的相关概念以及下文会用到的常用注解。

JMH 是一个用来构建,运转,剖析 Java 或其他运转在 JVM 之上的语言的纳秒/微秒/毫秒/微观等级基准测验的东西。它能够经过注解和装备参数来操控测验的环境和成果,例如预热次数,迭代次数,线程数,时刻单位等。它还能够生成具体的测验报告,包含最小值,均匀值,最大值,标准差,置信区间等。

JMH 相关概念

  • BeachMark:基准测验,主要用来测验一些办法的功能,能够依据不同的参数以不同的单位进行核算(能够运用均匀时刻作为单位,也能够运用吞吐量作为单位,能够在 BenchmarkMode 值进行调整)。
  • MIcro Benchmark:简略地说就是在 method 层面上的 benchmark,精度能够准确到微秒级。
  • OPS:Operation Per Second:每秒操作量,是衡量功能的重要目标,数值的功能越好。相似的有:TPS、QPS。
  • Throughput:吞吐量。
  • Warmup:预热,由于 JVM 的 JIT 机制的存储,假如某个函数被调用多次之后,JVM 会测验将其编译称为机器码从而提高履行速度。为了让成果愈加接近真实情况就需求进行预热。

JMH 注解介绍

  • @Benchmark:办法级注解,表明该办法是需求进行基准测验的目标,用法和 JUnit 的@Test相似。
  • @BenchmarkMode:类级或办法级注解,用来指定基准测验的形式。有以下几种形式可选:
    • Throughput:整体吞吐量,例如“1 秒内能够履行多少次调用”。
    • AverageTime:调用的均匀时刻,例如“每次调用均匀耗时 xxx 毫秒”。
    • SampleTime:随机取样,终究输出取样成果的散布,例如“99%的调用在 xxx 毫秒以内,99.99%的调用在 xxx 毫秒以内”。
    • SingleShotTime:单次调用时刻,合适用于冷启动测验,只运转一次,能够设置@Warmup(iterations = 0)来禁用预热。
    • All:上述一切形式的综合。
  • @OutputTimeUnit:类级或办法级注解,用来指定基准测验成果的时刻单位,可选的有NANOSECONDSMICROSECONDSMILLISECONDSSECONDS等。
  • @Warmup:类级或办法级注解,用来装备预热的参数,例如预热的次数,每次预热的时刻,时刻单位等。预热的目的是为了让 JVM 的 JIT 编译器对代码进行优化,使基准测验的成果愈加接近真实情况。
  • @Measurement:类级或办法级注解,用来装备实践履行基准测验的参数,例如测验的轮次,每轮的时刻,时刻单位等。
  • @Threads:类级或办法级注解,用来指定每个进程中的测验线程数,能够设置为Threads.MAX来运用一切可用的线程。
  • @Fork:类级或办法级注解,用来指定进行 fork 的次数。假如 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测验。

1.运用 Thread.currentThread().getStackTrace() 办法

基准测验参数装备如下

Warmup: 3 iterations, 10 s each
Measurement: 5 iterations, 3 s each
Timeout: 10 min per iteration
Threads: 8 threads, will synchronize iterations
Benchmark mode: Throughput

测验代码如下,

需求阐明的是下面的四种测验办法的 JMH 注解装备以及 main 办法都是相同的。所以为了节约篇幅,突出重点,后边三种计划将省去 JMH 注解以及 main 办法装备。

@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@Fork(2)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MethodNameTest {
    @Benchmark
    @BenchmarkMode({Mode.Throughput})
    public void m1() {
        // 获取当时办法名
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(MethodNameTest.class.getSimpleName())
                .build();
        new Runner(opt).run();
    }
}

测验成果如下,

...
# Run progress: 0.00% complete, ETA 00:06:00
# Fork: 1 of 2
# Warmup Iteration   1: 16.946 ops/ms
# Warmup Iteration   2: 17.086 ops/ms
# Warmup Iteration   3: 17.116 ops/ms
Iteration   1: 17.159 ops/ms
Iteration   2: 17.118 ops/ms
Iteration   3: 17.279 ops/ms
Iteration   4: 17.329 ops/ms
Iteration   5: 17.241 ops/ms
# Run progress: 12.50% complete, ETA 00:05:23
# Fork: 2 of 2
# Warmup Iteration   1: 16.546 ops/ms
# Warmup Iteration   2: 17.340 ops/ms
# Warmup Iteration   3: 17.431 ops/ms
Iteration   1: 17.331 ops/ms
Iteration   2: 17.099 ops/ms
Iteration   3: 17.280 ops/ms
Iteration   4: 17.511 ops/ms
Iteration   5: 17.323 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m1":
  17.267 (99.9%) 0.184 ops/ms [Average]
  (min, avg, max) = (17.099, 17.267, 17.511), stdev = 0.122
  CI (99.9%): [17.083, 17.451] (assumes normal distribution)

上面一大堆输出信息,我们直接看重点,在终究 Result "ltd.newbee.mall.MethodNameTest.m1": 这儿,均匀 ops 是每毫秒 17 次,比较低。

2.运用反常目标的 getStackTrace() 办法

测验代码如下,

@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m2() {
    // 获取当时办法名
    String methodName = new Throwable().getStackTrace()[0].getMethodName();
}

测验成果如下,

...
# Run progress: 25.00% complete, ETA 00:04:37
# Fork: 1 of 2
# Warmup Iteration   1: 12.891 ops/ms
# Warmup Iteration   2: 12.873 ops/ms
# Warmup Iteration   3: 13.023 ops/ms
Iteration   1: 25.617 ops/ms
Iteration   2: 25.840 ops/ms
Iteration   3: 25.301 ops/ms
Iteration   4: 24.839 ops/ms
Iteration   5: 25.930 ops/ms
# Run progress: 37.49% complete, ETA 00:03:51
# Fork: 2 of 2
# Warmup Iteration   1: 12.511 ops/ms
# Warmup Iteration   2: 12.329 ops/ms
# Warmup Iteration   3: 13.011 ops/ms
Iteration   1: 23.842 ops/ms
Iteration   2: 24.292 ops/ms
Iteration   3: 25.600 ops/ms
Iteration   4: 25.745 ops/ms
Iteration   5: 25.789 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m2":
  25.280 (99.9%) 1.088 ops/ms [Average]
  (min, avg, max) = (23.842, 25.280, 25.930), stdev = 0.720
  CI (99.9%): [24.191, 26.368] (assumes normal distribution)

直接看终究 Result "ltd.newbee.mall.MethodNameTest.m2": 这儿,均匀 ops 是每毫秒 25 次,也比较低。

3.运用匿名内部类的 getClass().getEnclosingMethod() 办法

测验代码如下,

@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m1() {
    // 获取当时办法名
    String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
}

测验成果如下,

...
# Run progress: 49.99% complete, ETA 00:03:04
# Fork: 1 of 2
# Warmup Iteration   1: 10489.110 ops/ms
# Warmup Iteration   2: 9233.590 ops/ms
# Warmup Iteration   3: 10504.615 ops/ms
Iteration   1: 10695.898 ops/ms
Iteration   2: 10570.155 ops/ms
Iteration   3: 11089.810 ops/ms
Iteration   4: 10805.448 ops/ms
Iteration   5: 10027.222 ops/ms
# Run progress: 62.49% complete, ETA 00:02:18
# Fork: 2 of 2
# Warmup Iteration   1: 11322.008 ops/ms
# Warmup Iteration   2: 10025.593 ops/ms
# Warmup Iteration   3: 10808.095 ops/ms
Iteration   1: 10684.594 ops/ms
Iteration   2: 11241.540 ops/ms
Iteration   3: 10742.348 ops/ms
Iteration   4: 9940.437 ops/ms
Iteration   5: 11226.023 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m3":
  10702.347 (99.9%) 672.631 ops/ms [Average]
  (min, avg, max) = (9940.437, 10702.347, 11241.540), stdev = 444.904
  CI (99.9%): [10029.716, 11374.979] (assumes normal distribution)

直接看终究 Result "ltd.newbee.mall.MethodNameTest.m3": 这儿,均匀 ops 是每毫秒 10702 次。十分夸大,能够看到 ops 对比上面两种办法一会儿从几十等级提高到上万等级。

4.Java 9 的 Stack-Walking API

测验代码如下,

@Benchmark
@BenchmarkMode({Mode.Throughput})
public void m1() {
    // 获取当时办法名
    String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
}

测验成果如下,

...
# Run progress: 74.99% complete, ETA 00:01:32
# Fork: 1 of 2
# Warmup Iteration   1: 2191.034 ops/ms
# Warmup Iteration   2: 2141.886 ops/ms
# Warmup Iteration   3: 2192.843 ops/ms
Iteration   1: 2262.279 ops/ms
Iteration   2: 2263.193 ops/ms
Iteration   3: 2201.354 ops/ms
Iteration   4: 2282.906 ops/ms
Iteration   5: 2130.322 ops/ms
# Run progress: 87.48% complete, ETA 00:00:46
# Fork: 2 of 2
# Warmup Iteration   1: 2207.800 ops/ms
# Warmup Iteration   2: 2269.887 ops/ms
# Warmup Iteration   3: 2239.005 ops/ms
Iteration   1: 2001.840 ops/ms
Iteration   2: 2047.698 ops/ms
Iteration   3: 2349.138 ops/ms
Iteration   4: 2362.165 ops/ms
Iteration   5: 2305.982 ops/ms
Result "ltd.newbee.mall.MethodNameTest.m4":
  2220.688 (99.9%) 186.910 ops/ms [Average]
  (min, avg, max) = (2001.840, 2220.688, 2362.165), stdev = 123.629
  CI (99.9%): [2033.778, 2407.598] (assumes normal distribution)

直接看终究 Result "ltd.newbee.mall.MethodNameTest.m4": 这儿,均匀 ops 是每毫秒 2220 次。对比 第一种和第二种计划的 几十 ops 来说功能提高也很客观,可是对比第三种办法的上万等级 ops 还是缺乏。

四种计划的终究得分对比

Benchmark           Mode  Cnt      Score     Error   Units
MethodNameTest.m1  thrpt   10     17.267    0.184  ops/ms
MethodNameTest.m2  thrpt   10     25.280    1.088  ops/ms
MethodNameTest.m3  thrpt   10  10702.347  672.631  ops/ms
MethodNameTest.m4  thrpt   10   2220.688  186.910  ops/ms
MethodNameTest.m1     ss   10      0.686    0.289   ms/op
MethodNameTest.m2     ss   10      0.339    0.287   ms/op
MethodNameTest.m3     ss   10      0.031    0.011   ms/op
MethodNameTest.m4     ss   10      0.074    0.027   ms/op

依据终究得分能够看出,四种计划中功能最好的计划是基于匿名内部类的 getClass().getEnclosingMethod() 计划,功能第二是的是基于 Java 9 出现的 Stack-Walking API 计划,其他两种功能过于低下了。

关注大众号【waynblog】每周共享技能干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!