首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JMH基准中的实地访问成本和恒定折叠

JMH基准中的实地访问成本和恒定折叠
EN

Stack Overflow用户
提问于 2022-04-04 11:55:35
回答 1查看 102关注 0票数 2

我正在Java 17上运行以下基准测试:

代码语言:javascript
复制
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ValueOfBenchmark {
    private final int value = 12345;

    @Benchmark
    public String concat() {
        return "" + value;
    }

    @Benchmark
    public String valueOf() {
        return String.valueOf(value);
    }
}

编译之后,我有了这个字节码

代码语言:javascript
复制
  public concat()Ljava/lang/String;
  @Lorg/openjdk/jmh/annotations/Benchmark;()
   L0
    LINENUMBER 21 L0
    LDC "12345"
    ARETURN
   L1
    LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public valueOf()Ljava/lang/String;
  @Lorg/openjdk/jmh/annotations/Benchmark;()
   L0
    LINENUMBER 26 L0
    SIPUSH 12345
    INVOKESTATIC java/lang/String.valueOf (I)Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

这里我得出结论,javac常量折叠了编译时访问字段的值。在concat方法中,它甚至进一步预测了将从该方法返回的最终值,但未能对valueOf()执行同样的操作,因此将12345的常量值推到堆栈上,随后调用String.valueOf()

该基准提供了以下结果:

代码语言:javascript
复制
Benchmark                 Mode  Cnt  Score   Error  Units
ValueOfBenchmark.concat   avgt   40  1,665 ± 0,006  ns/op
ValueOfBenchmark.valueOf  avgt   40  4,475 ± 0,217  ns/op

然后从final字段声明中删除value并重新编译基准测试:

代码语言:javascript
复制
public concat()Ljava/lang/String;
@Lorg/openjdk/jmh/annotations/Benchmark;()
 L0
  LINENUMBER 21 L0
  ALOAD 0
  GETFIELD com/tsypanov/ovn/ValueOfBenchmark.value : I
  INVOKEDYNAMIC makeConcatWithConstants(I)Ljava/lang/String; [
    // handle kind 0x6 : INVOKESTATIC
    java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    // arguments:
    "\u0001"
  ]
  ARETURN
 L1
  LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
  MAXSTACK = 1
  MAXLOCALS = 1

// access flags 0x1
public valueOf()Ljava/lang/String;
@Lorg/openjdk/jmh/annotations/Benchmark;()
 L0
  LINENUMBER 26 L0
  ALOAD 0
  GETFIELD com/tsypanov/ovn/ValueOfBenchmark.value : I
  INVOKESTATIC java/lang/String.valueOf (I)Ljava/lang/String;
  ARETURN
 L1
  LOCALVARIABLE this Lcom/tsypanov/ovn/ValueOfBenchmark; L0 L1 0
  MAXSTACK = 1
  MAXLOCALS = 1

现在我们有了invokedynamic for concat()方法,但是valueOf()基本上保持不变,唯一的区别是int值是从字段读取的。然而,这带来了结果的重大倒退:

代码语言:javascript
复制
Benchmark                 Mode  Cnt   Score   Error  Units
ValueOfBenchmark.concat   avgt   40   9,829 ± 0,059  ns/op
ValueOfBenchmark.valueOf  avgt   40  10,238 ± 0,463  ns/op

当然,我对concat()方法的期望是这样的,就像现在我们将两个值连接到一个字符串中一样。

但令我困惑的是valueOf()方法的回归。

我假设在基准测试方法中调用String.valueOf()的成本保持不变,那么为什么字段访问变得如此昂贵呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-04-04 21:54:39

我假设在基准测试方法中调用String.valueOf()的成本保持不变

事实并非如此。在第一种情况下,方法参数是常数,因此JIT可以应用常传播优化。

考虑一个非常简单的例子:

代码语言:javascript
复制
static char getLastDigit(int n) {
    return (char) ((n % 10) + '0');
}

char c1 = getLastDigit(123);

char c2 = getLastDigit(this.n);

当使用常量调用getLastDigit时,不需要执行实际的除法或加法-- JIT可以用char c1 = '3';替换整个调用--显然不能用变量进行相同的优化。

当然,Integer.getChars更复杂,但是当使用常量调用时,它仍然可以跳过一些比较和算术操作。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71736845

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档