性能调优之JMH必知必会3:编写正确的微基准测试用例

2023-05-16

性能调优之JMH必知必会3:编写正确的微基准测试用例

    • JMH必知必会系列文章(持续更新)
    • 一、前言
    • 二、编写正确的微基准测试用例
      • 1、添加JMH依赖包
      • 2、避免DCE(Dead Code Elimination)
      • 3、使用Balckhole
      • 4、避免常量折叠(Constant Folding)
      • 5、避免循环展开(Loop Unwinding)
      • 6、Fork用于避免 配置文件引导的优化(profile-guided optimizations)

 

JMH必知必会系列文章(持续更新)

  • 性能调优之JMH必知必会1:什么是JMH
  • 性能调优之JMH必知必会2:JMH的基本用法
  • 性能调优之JMH必知必会4:JMH的高级用法
  • 性能调优之JMH必知必会5:JMH的Profiler

 

一、前言

 
  在前面两篇文章中分别介绍了什么是JMH、JMH的基本法。现在来介绍JMH正确的微基准测试用例如何编写。【单位换算:1秒(s)=1000000微秒(us)=1000000000纳秒(ns)
 
  官方JMH源码(包含样例,在jmh-samples包里)下载地址:https://github.com/openjdk/jmh/tags。
 
  官方JMH样例在线浏览地址:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/。
 
  本文内容参考书籍《Java高并发编程详解:深入理解并发核心库》,作者为 汪文君 ,读者有需要可以去购买正版书籍。
 
  本文由 @大白有点菜 原创,请勿盗用,转载请说明出处!如果觉得文章还不错,请点点赞,加关注,谢谢!
 

二、编写正确的微基准测试用例

1、添加JMH依赖包

  在Maven仓库中搜索依赖包jmh-corejmh-generator-annprocess ,版本为 1.36 。需要注释 jmh-generator-annprocess 包中的“<scope>test</scope>”,不然项目运行会报错。

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.36</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.36</version>
<!--            <scope>test</scope>-->
</dependency>

2、避免DCE(Dead Code Elimination)

  所谓Dead Code Elimination是指JVM为我们擦去了一些上下文无关,甚至经过计算之后确定压根不会用到的代码,如下面的代码块。

public void test(){
    int x=10;
    int y=10;
    int z=x+y;
}

  我们在test方法中分别定义了x和y,并且经过相加运算得到了z,但是在该方法的下文中再也没有其他地方使用到z(既没有对z进行返回,也没有对其进行二次使用,z甚至不是一个全局的变量),JVM很有可能会将test()方法当作一个空的方法来看待,也就是说会擦除对x、y的定义,以及计算z的相关代码。
 
【验证在Java代码的执行过程中虚拟机是否会擦除与上下文无关的代码 - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:编写正确的微基准测试用例(避免DCE,即死码消除)
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_DCE {

    @Benchmark
    public void baseline(){
        // 空的方法
    }

    @Benchmark
    public void measureLog1(){
        // 进行数学运算,但是在局部方法内
        Math.log(Math.PI);
    }

    @Benchmark
    public void measureLog2(){
        // result是通过数学运算所得并且在下一行代码中得到了使用
        double result = Math.log(Math.PI);
        // 对result进行数学运算,但是结果既不保存也不返回,更不会进行二次运算
        Math.log(result);
    }

    @Benchmark
    public double measureLog3(){
        // 返回数学运算结果
        return Math.log(Math.PI);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【验证在Java代码的执行过程中虚拟机是否会擦除与上下文无关的代码 - 代码运行结果】

Benchmark                                               Mode  Cnt   Score    Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.baseline     avgt    510⁻⁴           us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog1  avgt    510⁻⁴           us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog2  avgt    510⁻⁴           us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_DCE.measureLog3  avgt    5   0.002 ±  0.001  us/op
  • baseline 方法作为一个空的方法,主要用于做基准数据。
  • measureLog1 中虽然进行了 log 运算,但是结果既没有再进行二次使用,也没有进行返回。
  • measureLog2 中同样进行了 log 运算,虽然第一次的运算结果是作为第二次入参来使用的,但是第二次执行结束后也再没有对其有更进一步的使用。
  • measureLog3 方法与 measureLog1 的方法类似,但是该方法对运算结果进行了返回操作。

  从输出结果看出,measureLog1 和 measureLog2 方法的基准性能与 baseline 几乎完全一致,因此我们可以肯定这两个方法中的代码进行过擦除操作,这样的代码被称为 Dead Code(死代码,其他地方都没有用到的代码片段),而 measureLog3 则与上述两个方法不同,由于它对结果进行了返回,因此 Math.log(PI) 不会被认为它是 Dead Code,因此它将占用一定的CPU时间。
 
  得出结论:要想编写性能良好的微基准测试方法,不要让方法存在Dead Code,最好每一个基准测试方法都有返回值
 
【附上官方Dead Code样例(JMHSample_08_DeadCode) - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:官方Dead Code样例
 * @author 大白有点菜
 */
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JmhTestApp14_DeadCode {

    /**
     * The downfall of many benchmarks is Dead-Code Elimination (DCE): compilers
     * are smart enough to deduce some computations are redundant and eliminate
     * them completely. If the eliminated part was our benchmarked code, we are
     * in trouble.
     *
     * 许多基准测试的失败是死代码消除(DCE):编译器足够聪明,可以推断出一些计算是多余的,并完全消除它们。
     * 如果被淘汰的部分是我们的基准代码,我们就有麻烦了。
     *
     * Fortunately, JMH provides the essential infrastructure to fight this
     * where appropriate: returning the result of the computation will ask JMH
     * to deal with the result to limit dead-code elimination (returned results
     * are implicitly consumed by Blackholes, see JMHSample_09_Blackholes).
     *
     * 幸运的是,JMH 提供了必要的基础设施来在适当的时候解决这个问题:返回计算结果将要求 JMH 处理结果
     * 以限制死代码消除(返回的结果被黑洞隐式消耗,请参阅 JMHSample_09_Blackholes)。
     */

    private double x = Math.PI;

    private double compute(double d) {
        for (int c = 0; c < 10; c++) {
            d = d * d / Math.PI;
        }
        return d;
    }

    @Benchmark
    public void baseline() {
        // do nothing, this is a baseline
    }

    @Benchmark
    public void measureWrong() {
        // This is wrong: result is not used and the entire computation is optimized away.
        compute(x);
    }

    @Benchmark
    public double measureRight() {
        // This is correct: the result is being used.
        return compute(x);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_DeadCode.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【附上官方Dead Code样例(JMHSample_08_DeadCode) - 代码运行结果】

Benchmark                           Mode  Cnt   Score   Error  Units
JmhTestApp14_DeadCode.baseline      avgt    5   0.261 ± 0.027  ns/op
JmhTestApp14_DeadCode.measureRight  avgt    5  13.920 ± 0.591  ns/op
JmhTestApp14_DeadCode.measureWrong  avgt    5   0.266 ± 0.039  ns/op

 
【官方Dead Code样例(JMHSample_08_DeadCode)注解 - 谷歌和百度翻译互补】

  许多基准测试的失败是死代码消除(DCE):编译器足够聪明,可以推断出一些计算是多余的,并完全消除它们。如果被淘汰的部分是我们的基准代码,我们就有麻烦了。
 
  幸运的是,JMH 提供了必要的基础设施来在适当的时候解决这个问题:返回计算结果将要求 JMH 处理结果以限制死代码消除(返回的结果被黑洞隐式消耗,请参阅 JMHSample_09_Blackholes)。

 

3、使用Balckhole

  假设在基准测试方法中,需要将两个计算结果作为返回值,那么我们该如何去做呢?我们第一时间想到的可能是将结果存放到某个数组或者容器当中作为返回值,但是这种对数组或者容器的操作会对性能统计造成干扰,因为对数组或者容器的写操作也是需要花费一定的CPU时间的。
 
  JMH提供了一个 Blackhole(黑洞) 类,可以在不作任何返回的情况下避免 Dead Code 的发生,与Linux系统下的黑洞设备 /dev/null 非常相似。
 
【Blackhole样例 - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:编写正确的微基准测试用例(使用Blackhole,即黑洞)
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole {

    double x1 = Math.PI;
    double x2 = Math.PI * 2;

    @Benchmark
    public double baseline()
    {
        // 不是Dead Code,因为对结果进行了返回
        return Math.pow(x1, 2);
    }

    @Benchmark
    public double powButReturnOne()
    {
        // Dead Code会被擦除
        Math.pow(x1, 2);
        // 不会被擦除,因为对结果进行了返回
        return Math.pow(x2, 2);
    }

    @Benchmark
    public double powThenAdd()
    {
        // 通过加法运算对两个结果进行了合并,因此两次的计算都会生效
        return Math.pow(x1, 2) + Math.pow(x2, 2);
    }

    @Benchmark
    public void useBlackhole(Blackhole hole)
    {
        // 将结果存放至black hole中,因此两次pow操作都会生效
        hole.consume(Math.pow(x1, 2));
        hole.consume(Math.pow(x2, 2));
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【Blackhole样例 - 代码运行结果】

Benchmark                                                             Mode  Cnt  Score   Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.baseline         avgt    5  2.126 ± 0.163  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.powButReturnOne  avgt    5  2.065 ± 0.112  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.powThenAdd       avgt    5  2.181 ± 0.151  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Blackhole.useBlackhole     avgt    5  3.748 ± 0.342  ns/op
  • baseline 方法中对 x1 进行了 pow 运算,之后返回,因此这个基准测试方法是非常合理的。
  • powButReturnOne 方法中的第一个 pow 运算仍然避免不了被当作 Dead Code 的命运,因此我们很难得到两次 pow 计算的方法耗时,但是对 x2 的 pow 运算会作为返回值返回,因此不是 Dead Code。
  • powThenAdd 方法就比较聪明,它同样会有返回值,两次 pow 操作也会被正常执行,但是由于采取的是加法运算,因此相加操作的CPU耗时也被计算到了两次 pow 操作中。
  • useBlackhole 方法中两次 pow 方法都会被执行,但是我们并没有对其进行返回操作,而是将其写入了 black hole 之中。

  输出结果表明,baseline 和 putButReturnOne 方法的性能几乎是一样的,powThenAdd 的性能相比前两个方法占用CPU的时间要稍微长一些,原因是该方法执行了两次 pow 操作。在 useBlackhole 中虽然没有对两个参数进行任何的合并操作,但是由于执行了 black hole 的 consume 方法,因此也会占用一定的CPU资源。虽然 blackhole 的 consume 方法会占用一定的CPU资源,但是如果在无返回值的基准测试方法中针对局部变量的使用都统一通过 blackhole 进行 consume ,那么就可以确保同样的基准执行条件,就好比拳击比赛时,对抗的拳手之间需要统一的体重量级一样。
 
  得出结论:Blackhole 可以帮助你在无返回值的基准测试方法中避免DC(Dead Code)情况的发生
 
【附上官方Blackhole样例(JMHSample_09_Blackholes) - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:官方Blackhole样例
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Balckhole {

    /**
     * Should your benchmark require returning multiple results, you have to
     * consider two options (detailed below).
     *
     * 如果您的基准测试需要返回多个结果,您必须考虑两个选项(详细信息如下)。
     *
     * NOTE: If you are only producing a single result, it is more readable to
     * use the implicit return, as in JMHSample_08_DeadCode. Do not make your benchmark
     * code less readable with explicit Blackholes!
     *
     * 注意:如果您只生成一个结果,使用隐式返回更具可读性,如 JMHSample_08_DeadCode。不要使用显式黑洞来降低基准代码的可读性!
     */

    double x1 = Math.PI;
    double x2 = Math.PI * 2;

    private double compute(double d) {
        for (int c = 0; c < 10; c++) {
            d = d * d / Math.PI;
        }
        return d;
    }

    /**
     * Baseline measurement: how much a single compute() costs.
     *
     * 基线测量:单个 compute() 的成本是多少。
     */

    @Benchmark
    public double baseline() {
        return compute(x1);
    }

    /**
     * While the compute(x2) computation is intact, compute(x1)
     * is redundant and optimized out.
     *
     * 虽然 compute(x2) 计算完好无损,但 compute(x1) 是多余的并经过优化。
     *
     */

    @Benchmark
    public double measureWrong() {
        compute(x1);
        return compute(x2);
    }

    /**
     * This demonstrates Option A:
     *
     * 这演示了选项 A:
     *
     * Merge multiple results into one and return it.
     * This is OK when is computation is relatively heavyweight, and merging
     * the results does not offset the results much.
     *
     * 将多个结果合并为一个并返回。当计算相对重量级时,这是可以的,并且合并结果不会抵消太多结果。
     */

    @Benchmark
    public double measureRight_1() {
        return compute(x1) + compute(x2);
    }

    /**
     * This demonstrates Option B:
     *
     * 这演示了选项 B:
     *
     * Use explicit Blackhole objects, and sink the values there.
     * (Background: Blackhole is just another @State object, bundled with JMH).
     * 
     * 使用明确的 Blackhole 对象,并将值下沉到那里。
     * (背景:Blackhole 只是另一个 @State 对象,与 JMH 捆绑在一起)。
     */

    @Benchmark
    public void measureRight_2(Blackhole bh) {
        bh.consume(compute(x1));
        bh.consume(compute(x2));
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Balckhole.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【附上官方Blackhole样例(JMHSample_09_Blackholes) - 代码运行结果】

Benchmark                              Mode  Cnt   Score   Error  Units
JmhTestApp14_Balckhole.baseline        avgt    5  13.691 ± 0.661  ns/op
JmhTestApp14_Balckhole.measureRight_1  avgt    5  22.318 ± 1.664  ns/op
JmhTestApp14_Balckhole.measureRight_2  avgt    5  26.079 ± 3.079  ns/op
JmhTestApp14_Balckhole.measureWrong    avgt    5  13.276 ± 1.931  ns/op

 
【官方Dead Code样例(JMHSample_09_Blackholes)注解 - 谷歌和百度翻译互补】

  见代码中的注释

 

4、避免常量折叠(Constant Folding)

  常量折叠是Java编译器早期的一种优化——编译优化。在javac对源文件进行编译的过程中,通过词法分析可以发现某些常量是可以被折叠的,也就是可以直接将计算结果存放到声明中,而不需要在执行阶段再次进行运算。比如:

private final int x = 10;
private final int y = x*20;

  在编译阶段,y的值将被直接赋予200,这就是所谓的常量折叠。
 
【Constant Folding样例 - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:编写正确的微基准测试用例(避免Constant Folding,即常量折叠)
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding {

    /**
     * x1和x2是使用final修饰的常量
     */
    private final double x1 = 124.456;
    private final double x2 = 342.456;

    /**
     * y1则是普通的成员变量
     */
    private double y1 = 124.456;
    /**
     * y2则是普通的成员变量
     */
    private double y2 = 342.456;

    /**
     * 直接返回124.456×342.456的计算结果,主要用它来作基准
     * @return
     */
    @Benchmark
    public double returnDirect()
    {
        return 42_620.703936d;
    }

    /**
     * 两个常量相乘,我们需要验证在编译器的早期优化阶段是否直接计算出了x1乘以x2的值
     * @return
     */
    @Benchmark
    public double returnCalculate_1()
    {
        return x1 * x2;
    }

    /**
     * 较为复杂的计算,计算两个未被final修饰的变量,主要也是用它来作为对比的基准
     * @return
     */
    @Benchmark
    public double returnCalculate_2()
    {
        return Math.log(y1) * Math.log(y2);
    }

    /**
     * 较为复杂的计算,操作的同样是final修饰的常量,查看是否在编译器优化阶段进行了常量的折叠行为
     * @return
     */
    @Benchmark
    public double returnCalculate_3()
    {
        return Math.log(x1) * Math.log(x2);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【Constant Folding样例 - 代码运行结果】

Benchmark                                                                      Mode  Cnt   Score   Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnCalculate_1  avgt    5   1.873 ± 0.119  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnCalculate_2  avgt    5  36.126 ± 2.372  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnCalculate_3  avgt    5   1.888 ± 0.169  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Constant_Folding.returnDirect       avgt    5   1.869 ± 0.115  ns/op

  我们可以看到,1、3、4三个方法的统计数据几乎相差无几,这也就意味着在编译器优化的时候发生了常量折叠,这些方法在运行阶段根本不需要再进行计算,直接将结果返回即可,而第二个方法的统计数据就没那么好看了,因为早期的编译阶段不会对其进行任何的优化。
 
【附上官方Constant Folding样例(JMHSample_10_ConstantFold) - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:官方Constant Folding样例
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_ConstantFolding {

    /**
     * The flip side of dead-code elimination is constant-folding.
     *
     * 死码消除的另一面是常量折叠。
     *
     * If JVM realizes the result of the computation is the same no matter what,
     * it can cleverly optimize it. In our case, that means we can move the
     * computation outside of the internal JMH loop.
     *
     * 如果 JVM 意识到无论如何计算的结果都是一样的,它可以巧妙地优化它。
     * 在我们的例子中,这意味着我们可以将计算移到内部 JMH 循环之外。
     *
     * This can be prevented by always reading the inputs from non-final
     * instance fields of @State objects, computing the result based on those
     * values, and follow the rules to prevent DCE.
     *
     * 这可以通过始终读取 @State 对象的非最终实例字段的输入,根据这些值计算结果,并遵循防止 DCE 的规则来防止。
     */

    // IDEs will say "Oh, you can convert this field to local variable". Don't. Trust. Them.
    // IDEs 会说“哦,你可以将这个字段转换为局部变量”。不要.相信.它们.
    // (While this is normally fine advice, it does not work in the context of measuring correctly.)
    // (虽然这通常是很好的建议,但它在正确测量的情况下不起作用。)
    private double x = Math.PI;

    // IDEs will probably also say "Look, it could be final". Don't. Trust. Them. Either.
    // IDEs 可能还会说“看,它可能是最终版本”。 也.不要.相信.它们.
    // (While this is normally fine advice, it does not work in the context of measuring correctly.)
    // (虽然这通常是很好的建议,但它在正确测量的情况下不起作用。)
    private final double wrongX = Math.PI;

    private double compute(double d) {
        for (int c = 0; c < 10; c++) {
            d = d * d / Math.PI;
        }
        return d;
    }

    @Benchmark
    public double baseline() {
        // simply return the value, this is a baseline
        // 简单地返回值,这是一个基线
        return Math.PI;
    }

    @Benchmark
    public double measureWrong_1() {
        // This is wrong: the source is predictable, and computation is foldable.
        // 这是错误的:来源是可预测的,计算是可折叠的。
        return compute(Math.PI);
    }

    @Benchmark
    public double measureWrong_2() {
        // This is wrong: the source is predictable, and computation is foldable.
        // 这是错误的:来源是可预测的,计算是可折叠的。
        return compute(wrongX);
    }

    @Benchmark
    public double measureRight() {
        // This is correct: the source is not predictable.
        // 这是正确的:来源是不可预测的。
        return compute(x);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_ConstantFolding.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【附上官方Constant Folding样例(JMHSample_10_ConstantFold) - 代码运行结果】

Benchmark                                    Mode  Cnt   Score   Error  Units
JmhTestApp14_ConstantFolding.baseline        avgt    5   1.871 ± 0.077  ns/op
JmhTestApp14_ConstantFolding.measureRight    avgt    5  13.989 ± 0.909  ns/op
JmhTestApp14_ConstantFolding.measureWrong_1  avgt    5   1.846 ± 0.075  ns/op
JmhTestApp14_ConstantFolding.measureWrong_2  avgt    5   1.870 ± 0.090  ns/op

 
【官方Constant Folding样例(JMHSample_10_ConstantFold)注解 - 谷歌和百度翻译互补】

  见代码中的注释

 

5、避免循环展开(Loop Unwinding)

  我们在编写JMH代码的时候,除了要避免Dead Code以及减少对常量的引用之外,还要尽可能地避免或者减少在基准测试方法中出现循环,因为循环代码在运行阶段(JVM后期优化)极有可能被“痛下杀手”进行相关的优化,这种优化被称为循环展开,下面我们来看一下什么是循环展开(Loop Unwinding)。

int sum=0;
for(int i = 0;i<100;i++){
    sum+=i;
}

  上面的例子中,sum=sum+i 这样的代码会被执行100次,也就是说,JVM会向CPU发送100次这样的计算指令,这看起来并没有什么,但是JVM的设计者们会认为这样的方式可以被优化成如下形式(可能)。

int sum=0;
for(int i = 0;i<20; i+=5){
    sum+=i;
    sum+=i+1;
    sum+=i+2;
    sum+=i+3;
    sum+=i+4;
}

  优化后将循环体中的计算指令批量发送给CPU,这种批量的方式可以提高计算的效率,假设1+2这样的运算执行一次需要1纳秒的CPU时间,那么在一个10次循环的计算中,我们觉得它可能是10纳秒的CPU时间,但是真实的计算情况可能不足10纳秒甚至更低。
 
【Loop Unwinding样例 - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:编写正确的微基准测试用例(避免Loop Unwinding,即循环展开)
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding {

    private int x = 1;
    private int y = 2;

    @Benchmark
    public int measure()
    {
        return (x + y);
    }

    private int loopCompute(int times)
    {
        int result = 0;
        for (int i = 0; i < times; i++)
        {
            result += (x + y);
        }
        return result;
    }

    @OperationsPerInvocation
    @Benchmark
    public int measureLoop_1()
    {
        return loopCompute(1);
    }

    @OperationsPerInvocation(10)
    @Benchmark
    public int measureLoop_10()
    {
        return loopCompute(10);
    }

    @OperationsPerInvocation(100)
    @Benchmark
    public int measureLoop_100()
    {
        return loopCompute(100);
    }

    @OperationsPerInvocation(1000)
    @Benchmark
    public int measureLoop_1000()
    {
        return loopCompute(1000);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【Loop Unwinding样例 - 代码运行结果】

Benchmark                                                                   Mode  Cnt  Score   Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measure           avgt    5  2.038 ± 0.167  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_1     avgt    5  2.112 ± 0.548  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_10    avgt    5  0.226 ± 0.013  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_100   avgt    5  0.026 ± 0.003  ns/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Loop_Unwinding.measureLoop_1000  avgt    5  0.023 ± 0.002  ns/op

  上面的代码中,measure() 方法进行了 x+y 的计算,measureLoop_1() 方法与 measure() 方法几乎是等价的,也是进行了 x+y 的计算,但是 measureLoop_10() 方法对 result+=(x+y) 进行了10次这样的操作,其实说白了就是调用了10次 measure() 或者 loopCompute(times=1) 。但是我们肯定不能直接拿10次的运算和1次运算所耗费的CPU时间去做比较,因此 @OperationsPerInvocation(10) 注解的作用就是在每一次 对measureLoop_10() 方法进行基准调用的时候将op操作记为10次。
 
  通过JMH的基准测试我们不难发现,在循环次数多的情况下,折叠的情况也比较多,因此性能会比较好,说明JVM在运行期对我们的代码进行了优化。
 
【附上官方Loop Unwinding样例(JMHSample_11_Loops) - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:官方Loop Unwinding样例
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_LoopUnwinding {

    /**
     * It would be tempting for users to do loops within the benchmarked method.
     * (This is the bad thing Caliper taught everyone). These tests explain why
     * this is a bad idea.
     *
     * 对于用户来说,在基准方法中进行循环是很有吸引力的。
     * (这是 Caliper 教给大家的坏事)。这些测试解释了为什么这是一个坏主意。
     *
     * Looping is done in the hope of minimizing the overhead of calling the
     * test method, by doing the operations inside the loop instead of inside
     * the method call. Don't buy this argument; you will see there is more
     * magic happening when we allow optimizers to merge the loop iterations.
     *
     * 循环是为了最小化调用测试方法的开销,通过在循环内而不是在方法调用内进行操作。
     * 不要相信这个论点; 当我们允许优化器合并循环迭代时,您会看到更多神奇的事情发生。
     */

    /**
     * Suppose we want to measure how much it takes to sum two integers:
     */

    int x = 1;
    int y = 2;

    /**
     * This is what you do with JMH.
     * 这是您使用JMH所做的。
     */

    @Benchmark
    public int measureRight() {
        return (x + y);
    }

    /**
     * The following tests emulate the naive looping.
     * 以下测试模拟了天真的循环。
     * This is the Caliper-style benchmark.
     * 这是 Caliper 风格的基准测试。
     */
    private int reps(int reps) {
        int s = 0;
        for (int i = 0; i < reps; i++) {
            s += (x + y);
        }
        return s;
    }

    /**
     * We would like to measure this with different repetitions count.
     * 我们想用不同的重复次数来衡量这一点。
     * Special annotation is used to get the individual operation cost.
     * 使用特殊注释来获得单个操作成本。
     */

    @Benchmark
    @OperationsPerInvocation(1)
    public int measureWrong_1() {
        return reps(1);
    }

    @Benchmark
    @OperationsPerInvocation(10)
    public int measureWrong_10() {
        return reps(10);
    }

    @Benchmark
    @OperationsPerInvocation(100)
    public int measureWrong_100() {
        return reps(100);
    }

    @Benchmark
    @OperationsPerInvocation(1_000)
    public int measureWrong_1000() {
        return reps(1_000);
    }

    @Benchmark
    @OperationsPerInvocation(10_000)
    public int measureWrong_10000() {
        return reps(10_000);
    }

    @Benchmark
    @OperationsPerInvocation(100_000)
    public int measureWrong_100000() {
        return reps(100_000);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_LoopUnwinding.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

 
【附上官方Loop Unwinding样例(JMHSample_11_Loops) - 代码运行结果】

Benchmark                                       Mode  Cnt  Score   Error  Units
JmhTestApp14_LoopUnwinding.measureRight         avgt    5  2.326 ± 0.089  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_1       avgt    5  2.052 ± 0.085  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_10      avgt    5  0.225 ± 0.006  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_100     avgt    5  0.026 ± 0.001  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_1000    avgt    5  0.022 ± 0.001  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_10000   avgt    5  0.019 ± 0.001  ns/op
JmhTestApp14_LoopUnwinding.measureWrong_100000  avgt    5  0.017 ± 0.002  ns/op

 
【官方Loop Unwinding样例(JMHSample_11_Loops)注解 - 谷歌和百度翻译互补】

  见代码中的注释

 

6、Fork用于避免 配置文件引导的优化(profile-guided optimizations)

  Fork是用来干什么的呢?本节将会为大家介绍Fork的作用以及JVM的配置文件引导的优化(profile-guided optimizations)。
 
  在开始解释Fork之前,我们想象一下平时是如何进行应用性能测试的,比如我们要测试一下Redis分别在50、100、200个线程中同时进行共计一亿次的写操作时的响应速度,一般会怎样做?首先,我们会将Redis库清空,尽可能地保证每一次测试的时候,不同的测试用例站在同样的起跑线上,比如,服务器内存的大小、服务器磁盘的大小、服务器CPU的大小等基本上相同,这样的对比才是有意义的,然后根据测试用例对其进行测试,接着清理Redis服务器资源,使其回到测试之前的状态,最后统计测试结果做出测试报告。
 
  Fork的引入也是考虑到了这个问题,虽然Java支持多线程,但是不支持多进程,这就导致了所有的代码都在一个进程中运行,相同的代码在不同时刻的执行可能会引入前一阶段对进程profiler的优化,甚至会混入其他代码profiler优化时的参数,这很有可能会导致我们所编写的微基准测试出现不准确的问题。对于这种说法大家可能会觉得有些抽象,通过例子去理解更好。
 
【Fork设置为0样例 - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:编写正确的微基准测试用例(Fork用于避免 profile-guided optimizations)
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
// 将Fork设置为0
@Fork(0)
// 将Fork设置为1
//@Fork(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Fork {

    // Inc1 和Inc2的实现完全一样
    interface Inc
    {
        int inc();
    }

    public static class Inc1 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    public static class Inc2 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    private Inc inc1 = new Inc1();
    private Inc inc2 = new Inc2();

    private int measure(Inc inc)
    {
        int result = 0;
        for (int i = 0; i < 10; i++)
        {
            result += inc.inc();
        }
        return result;
    }

    @Benchmark
    public int measure_inc_1()
    {
        return this.measure(inc1);
    }

    @Benchmark
    public int measure_inc_2()
    {
        return this.measure(inc2);
    }

    @Benchmark
    public int measure_inc_3()
    {
        return this.measure(inc1);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }
}

 
【Fork设置为0样例 - 代码运行结果】

Benchmark                                                      Mode  Cnt  Score    Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_1  avgt    5  0.002 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_2  avgt    5  0.012 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_3  avgt    5  0.012 ±  0.001  us/op

  将Fork设置为0,每一个基准测试方法都将会与 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 使用同一个JVM进程,因此基准测试方法可能会混入 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 进程的Profiler。
 
  measure_inc_1和 measure_inc_2 的实现方式几乎是一致的,它们的性能却存在着较大的差距,虽然 measure_inc_1 和 measure_inc_3 的代码实现完全相同,但还是存在着不同的性能数据,这其实就是JVM的 profiler-guided optimizations 导致的,由于我们所有的基准测试方法都与 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 的JVM进程共享,因此难免在其中混入 JmhTestApp14_Coding_Correct_Benchmark_Case_Fork 进程的Profiler,但是在将Fork设置为1的时候,也就是说每一次运行基准测试时都会开辟一个全新的JVM进程对其进行测试,那么多个基准测试之间将不会再存在干扰。
 
【Fork设置为1样例 - 代码】

package cn.zhuangyt.javabase.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:编写正确的微基准测试用例(Fork用于避免 profile-guided optimizations)
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
// 将Fork设置为0
//@Fork(0)
// 将Fork设置为1
@Fork(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Coding_Correct_Benchmark_Case_Fork {

    // Inc1 和Inc2的实现完全一样
    interface Inc
    {
        int inc();
    }

    public static class Inc1 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    public static class Inc2 implements Inc
    {
        private int i = 0;

        @Override
        public int inc()
        {
            return ++i;
        }
    }

    private Inc inc1 = new Inc1();
    private Inc inc2 = new Inc2();

    private int measure(Inc inc)
    {
        int result = 0;
        for (int i = 0; i < 10; i++)
        {
            result += inc.inc();
        }
        return result;
    }

    @Benchmark
    public int measure_inc_1()
    {
        return this.measure(inc1);
    }

    @Benchmark
    public int measure_inc_2()
    {
        return this.measure(inc2);
    }

    @Benchmark
    public int measure_inc_3()
    {
        return this.measure(inc1);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }
}

 
【Fork设置为1样例 - 代码运行结果】

Benchmark                                                      Mode  Cnt  Score    Error  Units
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_1  avgt    5  0.003 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_2  avgt    5  0.003 ±  0.001  us/op
JmhTestApp14_Coding_Correct_Benchmark_Case_Fork.measure_inc_3  avgt    5  0.003 ±  0.001  us/op

  以上输出是将Fork设置为1的结果,是不是合理了很多,若将Fork设置为0,则会与运行基准测试的类共享同样的进程Profiler,若设置为1则会为每一个基准测试方法开辟新的进程去运行,当然,你可以将Fork设置为大于1的数值,那么它将多次运行在不同的进程中,不过一般情况下,我们只需要将Fork设置为1即可。
 
【附上官方Fork样例(JMHSample_12_Forking) - 代码】

package cn.zhuangyt.javabase.jmh;

import cn.zhuangyt.javabase.jmh.jmh_sample.JMHSample_12_Forking;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * JMH测试14:官方Forks样例
 * @author 大白有点菜
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JmhTestApp14_Fork {

    /**
     * JVMs are notoriously good at profile-guided optimizations. This is bad
     * for benchmarks, because different tests can mix their profiles together,
     * and then render the "uniformly bad" code for every test. Forking (running
     * in a separate process) each test can help to evade this issue.
     *
     * JVM 以擅长配置文件引导的优化而著称。 这对基准测试不利,因为不同的测试可以将它们的配置文件混合在一起,
     * 然后为每个测试呈现“一致糟糕”的代码。 分叉(在单独的进程中运行)每个测试可以帮助避免这个问题。
     *
     * JMH will fork the tests by default.
     *
     * JMH 将默认分叉测试。
     */

    /**
     * Suppose we have this simple counter interface, and two implementations.
     * Even though those are semantically the same, from the JVM standpoint,
     * those are distinct classes.
     *
     * 假设我们有这个简单的计数器接口和两个实现。尽管它们在语义上是相同的,但从 JVM 的角度来看,它们是不同的类。
     */

    public interface Counter {
        int inc();
    }

    public static class Counter1 implements JMHSample_12_Forking.Counter {
        private int x;

        @Override
        public int inc() {
            return x++;
        }
    }

    public static class Counter2 implements JMHSample_12_Forking.Counter {
        private int x;

        @Override
        public int inc() {
            return x++;
        }
    }

    /**
     * And this is how we measure it.
     * 这就是我们衡量它的方式。
     * Note this is susceptible for same issue with loops we mention in previous examples.
     * 请注意,这很容易受到我们在前面示例中提到的循环的相同问题的影响。
     */

    public int measure(JMHSample_12_Forking.Counter c) {
        int s = 0;
        for (int i = 0; i < 10; i++) {
            s += c.inc();
        }
        return s;
    }

    /**
     * These are two counters.
     */
    JMHSample_12_Forking.Counter c1 = new JMHSample_12_Forking.Counter1();
    JMHSample_12_Forking.Counter c2 = new JMHSample_12_Forking.Counter2();

    /**
     * We first measure the Counter1 alone...
     * 我们首先单独测量 Counter1 ...
     * Fork(0) helps to run in the same JVM.
     * Fork(0) 有助于在同一个 JVM 中运行。
     */

    @Benchmark
    @Fork(0)
    public int measure_1_c1() {
        return measure(c1);
    }

    /**
     * Then Counter2...
     * 然后到 Counter2...
     */

    @Benchmark
    @Fork(0)
    public int measure_2_c2() {
        return measure(c2);
    }

    /**
     * Then Counter1 again...
     * 然后再次到 Counter1
     */

    @Benchmark
    @Fork(0)
    public int measure_3_c1_again() {
        return measure(c1);
    }

    /**
     * These two tests have explicit @Fork annotation.
     * JMH takes this annotation as the request to run the test in the forked JVM.
     * It's even simpler to force this behavior for all the tests via the command
     * line option "-f". The forking is default, but we still use the annotation
     * for the consistency.
     *
     * 这两个测试有显示的 @Fork 注释。
     * JMH 将此注释作为在分叉的 JVM 中运行测试的请求。
     * 通过命令行选项“-f”为所有测试强制执行此行为甚至更简单。 分叉是默认的,但我们仍然使用注释来保持一致性。
     *
     * This is the test for Counter1.
     * 这是 Counter1 的测试
     */

    @Benchmark
    @Fork(1)
    public int measure_4_forked_c1() {
        return measure(c1);
    }

    /**
     * ...and this is the test for Counter2.
     * 还有这是 Counter2 的测试
     */

    @Benchmark
    @Fork(1)
    public int measure_5_forked_c2() {
        return measure(c2);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JmhTestApp14_Fork.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }
}

 
【附上官方Fork样例(JMHSample_12_Forking) - 代码运行结果】

Benchmark                              Mode  Cnt   Score   Error  Units
JmhTestApp14_Fork.measure_1_c1         avgt    5   2.162 ± 0.129  ns/op
JmhTestApp14_Fork.measure_2_c2         avgt    5  12.490 ± 0.304  ns/op
JmhTestApp14_Fork.measure_3_c1_again   avgt    5  12.182 ± 0.605  ns/op
JmhTestApp14_Fork.measure_4_forked_c1  avgt    5   3.138 ± 0.162  ns/op
JmhTestApp14_Fork.measure_5_forked_c2  avgt    5   3.179 ± 0.302  ns/op

 
【官方Fork样例注解(JMHSample_12_Forking) - 谷歌和百度翻译互补】

  见代码中的注释

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

性能调优之JMH必知必会3:编写正确的微基准测试用例 的相关文章

  • 利用uORB机制实现数据在不同进程中通信

    uORB实际上是一种设计模式中的观察者模式 xff0c 用于实现一种一对多的依赖关系 xff0c 让多个观察者 xff08 Observer xff09 同时监听某一主题对象 xff08 Subject xff09 当这个主题对象 xff0
  • Android-注解篇

    1 什么是注解 从JDK 5 开始 xff0c Java 增加了注解 xff0c 注解是代码里的特殊标记 xff0c 这些标记可以在编译 类加载 运 行时被读取 xff0c 并执行相应的处理 通过使用注解 xff0c 开发人员可以在不改变原
  • 新品BCM6755A1KFEBG/MT7921LE/MT7921AU WiFi芯片

    博通在WiFi市场具有相当的实力 在WiFi6上有下面这几个解决方案 xff1a 型号 xff1a BCM6755 BCM6755A1KFEBG 类型 xff1a 四核1 5GHz CPU 封装 xff1a BGA 批次 xff1a 新 B
  • Ubuntu : GPG签名验证错误 解决之道sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 6DFBCBAE

    Ubuntu GPG签名验证错误 解决之道 转载 sudo apt key adv keyserver keyserver ubuntu com recv keys Key Where key 61 61 the gpg key id Th
  • T265深度图像输出

    1 T265深度图像输出 1 1 环境依赖 T265摄像头python3pip3opencv pythonpyrealsense2 1 2 安装运行环境 安装秘钥 span class token function sudo span ap
  • Linux版本号串记录(ubuntu系列)

    Linux version 4 4 0 112 generic buildd 64 lgw01 amd64 010 gcc version 5 4 0 20160609 Ubuntu 5 4 0 6ubuntu1 16 04 5 135 U
  • 死锁的四个必要条件

    死锁 在高并发中是一个常见的名词 产生的四个必要条件如下 xff1a 互斥条件 xff1a 一个资源同一时间能且只能被一个线程访问 xff1b 不可掠夺 xff1a 当资源被一个线程占用时 xff0c 其他线程不可抢夺该资源 xff1b 请
  • Sphinx index.rst

    假设我们有两个文本file1 rst和file2 rst他们的内容如下 file1 rst span class hljs header file1 title1 61 61 61 61 61 61 61 61 61 61 61 61 sp
  • Git - 图形化界面操作

    目录 1 新建仓库 2 代码提交 3 代码回滚 4 新建分支 5 合并分支 6 重置合并 7 分支变基 8 分支优选 Git 的图形化界面操作 xff0c 使用 Idea 进行演示 1 新建仓库 对于一个代码仓库 Create Git re
  • CMakeLists

    1 指定 cmake 的最小版本 cmake minimum required VERSION 3 4 1 2 设置项目名称 xff0c 它会引入两个变量 demo BINARY DIR 和 demo SOURCE DIR xff0c 同时
  • 七步实现STM32MP157多核协同工作(Cortex-A7与Cortex-M4通信)

    写在前面 xff1a STM32MP157是ST进军Linux的首款微处理器 xff0c 采用MCU 43 MPU的组合 xff0c 集成两颗主频微800MHz的Cortex A7应用处理器内核 xff08 支持开源linux操作系统 xf
  • 【实战】STM32 FreeRTOS移植系列教程4:FreeRTOS 软件定时器

    写在前面 xff1a 本文章为 STM32MP157开发教程之FreeRTOS操作系统篇 系列中的一篇 xff0c 笔者使用的开发平台为华清远见FS MP1A开发板 xff08 STM32MP157开发板 xff09 stm32mp157是
  • 【实战】STM32 FreeRTOS移植系列教程5:FreeRTOS消息队列

    写在前面 xff1a 本文章为 STM32MP157开发教程之FreeRTOS操作系统篇 系列中的一篇 xff0c 笔者使用的开发平台为华清远见FS MP1A开发板 xff08 STM32MP157开发板 xff09 stm32mp157是
  • 学习嵌入式linux为什么推荐stm32mp157开发板?

    stm32mp157是ST推出的一款双A7 43 M4多核异构处理器 xff0c 既可以学习linux xff0c 又可以学习stm32单片机开发 xff0c 还可以拓展物联网 人工智能方向技术学习 xff0c 并极大丰富linux应用场景
  • STM32 Linux开发板——教程+视频+项目+硬件

    STM32 Linux开发板 适合入门进阶学习的Linux开发板 xff1a 华清远见FS MP1A开发板 xff08 STM32MP157开发板 xff09 开发板介绍 FS MP1A开发板是华清远见自主研发的一款高品质 高性价比的Lin
  • 编程语言对比 面向对象

    C 43 43 面向对象 java面向对象 python面向对象 java中是public int a 61 10 C 43 43 中是 public int a 61 10 C 43 43 中有拷贝构造
  • 嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板

    stm32mp157开发板FS MP1A是华清远见自主研发的一款高品质 高性价比的Linux 43 单片机二合一的嵌入式教学级开发板 开发板搭载ST的STM32MP157高性能微处理器 xff0c 集成2个Cortex A7核和1个Cort
  • CMake(一)

    CMake xff08 一 xff09 简述 在之前的文章中介绍了 qmake的使用 相比qmake xff0c CMake稍微复杂一点 xff0c 它使用CMakeList txt文件来定制整个编译流程 同时 xff0c CMake会根据
  • LTE网元功能

    LTE 网元功能 2015 03 30 22 33 31 分类 xff1a NetworkProtocols 举报 字号 订阅 下载LOFTER 我的照片书 主要网元功能 eNodeB Radio Resou
  • [C++] 32位C++程序,计算sizeof的值

    sizeof str 61 6 字符串数组 xff0c 大小是六个字节 加上 39 0 39 共六个 sizeof p 61 4 指针的内容就是一个指向目标地址的整数 xff0c 所以不管指向char int还是其他 xff0c 32位机指

随机推荐

  • 串口打印printf

    串口打印printf 1 配置串口2 添加代码3 使用MDK勾选Mircro LIB 1 配置串口 打开STM32CubeMX xff0c 创建工程 xff0c 配置串口 2 添加代码 重写fputc函数 xff0c 需要包含头文件 inc
  • 22.Ubuntu出现“由于没有公钥,无法验证下列签名”

    由于没有公钥 xff0c 无法验证下列签名 1 无公钥错误2 输入命令导入公钥3 注意 1 无公钥错误 使用sudo apt update时出现以下错误 xff1a 我图中的公钥就是 xff1a 3B4FE6ACC0B21F32 xff08
  • nyist 27 水池数目(dfs搜索)

    xfeff xfeff 水池数目 时间限制 xff1a 3000 ms 内存限制 xff1a 65535 KB 难度 xff1a 4 描述 南阳理工学院校园里有一些小河和一些湖泊 xff0c 现在 xff0c 我们把它们通一看成水池 xff
  • XTUOJ 1176 I Love Military Chess(模拟)

    xfeff xfeff I Love Military Chess Accepted 45 Submit 141Time Limit 1000 MS Memory Limit 65536 KB 题目描述 陆军棋 xff0c 又称陆战棋 xf
  • 数据结构课程设计之一元多项式的计算

    数据结构不是听会的 xff0c 也不是看会的 xff0c 是练会的 xff0c 对于写这么长的代码还是心有余也力不足啊 xff0c 对于指针的一些操作 xff0c 也还是不熟练 xff0c 总出现一些异常错误 xff0c 对于数据结构掌握还
  • Unity官方文档(英文)

    地址 xff1a https docs unity3d com Manual UnityManual html
  • 数据结构课程设计之通讯录管理系统

    数据结构的第二个课程设计 xff0c 在c语言课程设计的基础上加以改进 xff0c xff08 加强版 xff09 xff0c 保存一下代码 xff0c 对文件的处理 xff0c 还是有一点一问题 xff0c 还有待改进 include l
  • 在网页中添加音乐

    最近在折腾一个网页 xff0c 对于一个有强迫症的人来说 xff0c 就想在网页中插入音乐 xff0c xff08 当做背景音乐 xff09 xff0c 然后自己百度了好多资料 xff1b 就在这里总结一下 xff1a 第一步 xff1a
  • nyist oj 214 单调递增子序列(二) (动态规划经典)

    单调递增子序列 二 时间限制 xff1a 1000 ms 内存限制 xff1a 65535 KB 难度 xff1a 4 描述 给定一整型数列 a1 a2 an xff08 0 lt n lt 61 100000 xff09 xff0c 找出
  • 思科CCNA第一学期期末考试答案

    1 第 3 层头部包含的哪一项信息可帮助数据传输 xff1f 端口号 设备物理地址 目的主机逻辑地址 虚拟连接标识符 2 IP 依靠 OSI 哪一层的协议来确定数据包是否已丢失并请求重传 xff1f 应用层 表示层 会话层 传输层 3 请参
  • hexo博客出现command not found解决方案

    由于前一段时间忙于考试 xff0c 也有好久没有去更新博客了 xff0c 今天去添加友链的时候 xff0c 突然发现用不了了 xff0c 出现了conmand not found的提示 xff1a 按照字面上的翻译就是 找不到所使用的命令
  • 思科CCNA第二学期期末考试答案

    1 关于数据包通过路由器传输时的封装和解封的叙述 xff0c 下列哪三项是正确的 xff1f xff08 选择三项 xff09 路由器修改 TTL 字段 xff0c 将其值减 1 路由器将源 IP 更改为送出接口的 IP 路由器保持相同的源
  • Hexo版本升级和Next主题升级之坑

    缘起 差不多用了一年hexo的3 2 0版本 xff0c next主题版本也用的5 0的 xff0c 本来用的好好的 xff0c 但是最近访问其他人的博客 xff0c 发现访问速度比我的提升了不止一点点 xff0c 遂决定折腾一番 过程 H
  • Python中JSON的基本使用

    JSON JavaScript Object Notation 是一种轻量级的数据交换格式 Python3 中可以使用 json 模块来对 JSON 数据进行编解码 xff0c 它主要提供了四个方法 xff1a dumps dump loa
  • 卷积和快速傅里叶变换(FFT)的实现

    卷积运算 卷积可以说是图像处理中最基本的操作 线性滤波通过不同的卷积核 xff0c 可以产生很多不同的效果 假如有一个要处理的二维图像 xff0c 通过二维的滤波矩阵 xff08 卷积核 xff09 xff0c 对于图像的每一个像素点 xf
  • 每个程序员和设计师可做的10项运动

    本文转载自 码农网 程序员 和设计师大部分时间都坐在电脑前 有效的锻炼有助于他们更好地工作 传统的 xff1a 当坐在电脑桌前的时候 脚触地 双手在肘部弯曲 打字时手应搁在桌子上 键盘和鼠标应在触手可及的地方 显示屏应在视线水平上 xff0
  • OKhttpUtils

    public class OkUtils static final int GET EXCUTE 61 111 static final int GET ENQUEUE 61 222 static final int POST EXCUTE
  • 性能调优之JMH必知必会4:JMH的高级用法

    性能调优之JMH必知必会4 xff1a JMH的高级用法 JMH必知必会系列文章 xff08 持续更新 xff09 一 前言二 JMH的高级用法1 添加JMH依赖包2 Asymmetric Benchmark3 Interrupts Ben
  • 性能调优之JMH必知必会5:JMH的Profiler

    性能调优之JMH必知必会5 xff1a JMH的Profiler JMH必知必会系列文章 xff08 持续更新 xff09 一 前言二 JMH的Profiler1 添加JMH依赖包2 StackProfiler3 GcProfiler4 C
  • 性能调优之JMH必知必会3:编写正确的微基准测试用例

    性能调优之JMH必知必会3 xff1a 编写正确的微基准测试用例 JMH必知必会系列文章 xff08 持续更新 xff09 一 前言二 编写正确的微基准测试用例1 添加JMH依赖包2 避免DCE xff08 Dead Code Elimin