为什么Scala中没有AnyVal和泛型的分配

问题描述

我的基准测试结果出乎意料。

该基准测试的目的是说明Scala AnyVal在泛型中的表现不佳。我创建了一个类型AnyValId并扩展了AnyVal

预期结果是在调用通用方法containsidentity0时使用gc分析器查看分配情况。

您能帮我弄清楚这里发生了什么吗?

这是板凳

import java.util.concurrent.TimeUnit

import org.openjdk.jmh.annotations._

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1,jvmArgsAppend = Array("-Djmh.stack.lines=3"))
@Threads(1)
@Warmup(iterations = 3,time = 1,timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3,timeUnit = TimeUnit.SECONDS)
class MapBench {
  var set: Set[AnyValId] = _
  var id: AnyValId       = _

  @Setup
  def setup() = {
    set = Set(AnyValId(1))
    id = AnyValId(1)
  }

  @Benchmark
  def _idWith(): AnyValId = identity0(id)

  @Benchmark
  def contains(): Boolean =
    set.contains(id)

  def identity0[T](t: T) = t

}

case class AnyValId(i: Int) extends AnyVal

这是结果

[info] Benchmark                              Mode  Cnt   Score    Error   Units
[info] MapBench._idWith                       avgt    3   2,128 ±  2,015   ns/op
[info] MapBench._idWith:·gc.alloc.rate        avgt    3  ≈ 10⁻⁴           MB/sec
[info] MapBench._idWith:·gc.alloc.rate.norm   avgt    3  ≈ 10⁻⁶             B/op
[info] MapBench._idWith:·gc.count             avgt    3     ≈ 0           counts
[info] MapBench.contains                      avgt    3   3,985 ±  0,668   ns/op
[info] MapBench.contains:·gc.alloc.rate       avgt    3  ≈ 10⁻⁴           MB/sec
[info] MapBench.contains:·gc.alloc.rate.norm  avgt    3  ≈ 10⁻⁶             B/op
[info] MapBench.contains:·gc.count            avgt    3     ≈ 0           counts

通过REPL中的进一步调查进行更新

scala> case class Id(i: Int)
class Id

scala> Set(Id(1))
val res3: scala.collection.immutable.Set[Id] = Set(Id(1))

scala> def f = res3.contains(Id(2))
def f: Boolean

:javap f

这是字节码,因为包含是在Id上参数化的,似乎需要Id的实例才能调用它。因此,new指令以字节码表示。 因此,REPL和JMH基准测试中的行为似乎有所不同。

public boolean f();
  descriptor: ()Z
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=5,locals=1,args_size=1
       0: aload_0
       1: invokevirtual #29                 // Method $line32$$read$$iw$$$outer:()L$line32/$read;
       4: invokevirtual #33                 // Method $line32/$read.$line30$read:()L$line30/$read;
       7: invokevirtual #36                 // Method $line30/$read.$iw:()L$line30/$read$$iw;
      10: invokevirtual #40                 // Method $line30/$read$$iw.res3:()Lscala/collection/immutable/Set;
      13: new           #14                 // class $line29/$read$$iw$Id
      16: dup
      17: getstatic     #46                 // Field $line29/$read$.MODULE$:L$line29/$read$;
      20: invokevirtual #50                 // Method $line29/$read$.INSTANCE:()L$line29/$read;
      23: invokevirtual #53                 // Method $line29/$read.$iw:()L$line29/$read$$iw;
      26: iconst_2
      27: invokespecial #57                 // Method $line29/$read$$iw$Id."<init>":(L$line29/$read$$iw;I)V
      30: invokeinterface #63,2           // InterfaceMethod scala/collection/immutable/Set.contains:(Ljava/lang/Object;)Z
      35: ireturn
    LineNumberTable:
      line 1: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      36     0  this   L$line32/$read$$iw;

解决方法

我想我了解发生了什么。我在基准测试中犯了两个错误

  1. Set仅包含一个元素,因此与Set$Set1而非{{ 1}}。因此,我已修复了设置以使用更长的Set的问题。

  2. 因此,将调用contains。它通过调用equals将int装箱。但是,此方法在内部使用缓存。因此,因为整数在[-127,128]范围内,所以没有分配。

再说一次,这个免责声明确实有意义。

记住:以下数字仅为数据。为了获得可重用的见解, 您需要了解数字为何如此。

enter image description here

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...