Skip to content

Cache materialized TypeTags#8112

Merged
eed3si9n merged 1 commit intoscala:2.12.xfrom
retronym:topic/typetag-cache
Jul 23, 2019
Merged

Cache materialized TypeTags#8112
eed3si9n merged 1 commit intoscala:2.12.xfrom
retronym:topic/typetag-cache

Conversation

@retronym
Copy link
Member

@retronym retronym commented May 31, 2019

Type tags summoned with universe.typeTag or an implicit search are
expanded thusly:

object Test {

  def materializeTag = reflect.runtime.universe.typeTag[Option[String]]

  def main(args: Array[String]): Unit = {
    val tag1 = materializeTag
    val tag2 = materializeTag
    println(tag1 eq tag2)
  }
}
    def materializeTag: reflect.runtime.universe.TypeTag[Option[String]] = scala.reflect.runtime.`package`.universe.typeTag[Option[String]](({
      val $u: reflect.runtime.universe.type = scala.this.reflect.runtime.`package`.universe;
      val $m: $u.Mirror = scala.this.reflect.runtime.`package`.universe.runtimeMirror(this.getClass().getClassLoader());
      $u.TypeTag.apply[Option[String]]($m, {
        final class $typecreator1 extends TypeCreator {
          def <init>(): $typecreator1 = {
            $typecreator1.super.<init>();
            ()
          };
          def apply[U <: scala.reflect.api.Universe with Singleton]($m$untyped: scala.reflect.api.Mirror[U]): U#Type = {
            val $u: U = $m$untyped.universe;
            val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror];
            $u.internal.reificationSupport.TypeRef($u.internal.reificationSupport.ThisType($m.staticPackage("scala").asModule.moduleClass), $m.staticClass("scala.Option"), scala.collection.immutable.List.apply[$u.Type]($u.internal.reificationSupport.TypeRef($u.internal.reificationSupport.SingleType($m.staticPackage("scala").asModule.moduleClass.asType.toTypeConstructor, $m.staticModule("scala.Predef")), $u.internal.reificationSupport.selectType($m.staticModule("scala.Predef").asModule.moduleClass, "String"), scala.collection.immutable.Nil)))
          }
        };
        new $typecreator1()
      })
    }: reflect.runtime.universe.TypeTag[Option[String]]));

A new TypeTag is created time def materializeTag is called above; the program prints false.

This commit introduces a cache, keyed by the synthetic $typecreator1, and hosted in the JavaMirror.
We know that the apply method is a pure, so the caching is sound.

Using ClassValue means that we're not introducing a classloader leak.

We are extending the lifetime of the TypeTag and contained type itself, which represents a small
risk to existing applications, so I've included an opt-out System property.

@scala-jenkins scala-jenkins added this to the 2.12.9 milestone May 31, 2019
@retronym retronym added the performance the need for speed. usually compiler performance, sometimes runtime performance. label May 31, 2019
@retronym
Copy link
Member Author

/cc @mkeskells @DanielaSfregola

@retronym retronym force-pushed the topic/typetag-cache branch 2 times, most recently from d9cc5e6 to 59524e6 Compare May 31, 2019 03:49
@retronym retronym requested a review from hrhino May 31, 2019 03:54
@retronym retronym force-pushed the topic/typetag-cache branch 2 times, most recently from 156a760 to a5a34d0 Compare May 31, 2019 04:04
@retronym
Copy link
Member Author

Waiting for lock on /home/jenkins/.sbt/boot/sbt.boot.lock to be available...
Error wrapping InputStream in GZIPInputStream: java.io.EOFException
	at sbt.ErrorHandling$.translate(ErrorHandling.scala:10)
	at sbt.WrapUsing.open(Using.scala:34)
	at sbt.Using.apply(Using.scala:23)
	at sbt.IO$$anonfun$gzipFileIn$1.apply(IO.scala:877)
	at sbt.IO$$anonfun$gzipFileIn$1.apply(IO.scala:876)
	at sbt.Using.apply(Using.scala:24)
	at sbt.IO$.gzipFileIn(IO.scala:876)
	at sbt.Sync$.readUncaught(Sync.scala:97)
	at sbt.Sync$.readInfo(Sync.scala:85)
	at sbt.Sync$$anonfun$apply$1.apply(Sync.scala:28)
	at sbt.Sync$$anonfun$apply$1.apply(Sync.scala:22)
	at sbt.Defaults$$anonfun$copyResourcesTask$1.apply(Defaults.scala:989)
	at sbt.Defaults$$anonfun$copyResourcesTask$1.apply(Defaults.scala:985)
	at scala.Function4$$anonfun$tupled$1.apply(Function4.scala:35)
	at scala.Function4$$anonfun$tupled$1.apply(Function4.scala:34)
	at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
	at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
	at sbt.std.Transform$$anon$4.work(System.scala:63)
	at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:228)
	at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:228)
	at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
	at sbt.Execute.work(Execute.scala:237)
	at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:228)
	at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:228)
	at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
	at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.EOFException
	at java.util.zip.GZIPInputStream.readUByte(GZIPInputStream.java:268)
	at java.util.zip.GZIPInputStream.readUShort(GZIPInputStream.java:258)
	at java.util.zip.GZIPInputStream.readHeader(GZIPInputStream.java:164)
	at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:79)
	at sbt.Using$$anonfun$gzipInputStream$1.apply(Using.scala:84)
	at sbt.Using$$anonfun$gzipInputStream$1.apply(Using.scala:84)
	at sbt.Using$$anon$1.openImpl(Using.scala:51)
	at sbt.WrapUsing$$anonfun$open$2.apply(Using.scala:34)
	at sbt.ErrorHandling$.translate(ErrorHandling.scala:10)
	at sbt.WrapUsing.open(Using.scala:34)
	at sbt.Using.apply(Using.scala:23)
	at sbt.IO$$anonfun$gzipFileIn$1.apply(IO.scala:877)
	at sbt.IO$$anonfun$gzipFileIn$1.apply(IO.scala:876)
	at sbt.Using.apply(Using.scala:24)
	at sbt.IO$.gzipFileIn(IO.scala:876)
	at sbt.Sync$.readUncaught(Sync.scala:97)
	at sbt.Sync$.readInfo(Sync.scala:85)
	at sbt.Sync$$anonfun$apply$1.apply(Sync.scala:28)
	at sbt.Sync$$anonfun$apply$1.apply(Sync.scala:22)
	at sbt.Defaults$$anonfun$copyResourcesTask$1.apply(Defaults.scala:989)
	at sbt.Defaults$$anonfun$copyResourcesTask$1.apply(Defaults.scala:985)
	at scala.Function4$$anonfun$tupled$1.apply(Function4.scala:35)
	at scala.Function4$$anonfun$tupled$1.apply(Function4.scala:34)
	at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
	at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
	at sbt.std.Transform$$anon$4.work(System.scala:63)
	at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:228)
	at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:228)
	at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
	at sbt.Execute.work(Execute.scala:237)
	at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:228)
	at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:228)
	at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
	at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
[error] (compile:copyResources) Error wrapping InputStream in GZIPInputStream: java.io.EOFException

@retronym
Copy link
Member Author

/rebuild

@retronym retronym force-pushed the topic/typetag-cache branch 4 times, most recently from 3ca05b7 to 0b581ef Compare May 31, 2019 05:22
@retronym retronym force-pushed the topic/typetag-cache branch from 0b581ef to 3ebf76d Compare June 19, 2019 08:44
@retronym
Copy link
Member Author

/rebuild

@retronym
Copy link
Member Author

The test failure in PipelineMainTest is unrelated to this PR. It is a new flaky test I need to deflake.

@SethTisue SethTisue added the release-notes worth highlighting in next release notes label Jun 20, 2019
Type tags summoned with `universe.typeTag` or an implicit search are
expanded thusly:

```
object Test {

  def materializeTag = reflect.runtime.universe.typeTag[Option[String]]

  def main(args: Array[String]): Unit = {
    val tag1 = materializeTag
    val tag2 = materializeTag
    println(tag1 eq tag2)
  }
}
```

```
    def materializeTag: reflect.runtime.universe.TypeTag[Option[String]] = scala.reflect.runtime.`package`.universe.typeTag[Option[String]](({
      val $u: reflect.runtime.universe.type = scala.this.reflect.runtime.`package`.universe;
      val $m: $u.Mirror = scala.this.reflect.runtime.`package`.universe.runtimeMirror(this.getClass().getClassLoader());
      $u.TypeTag.apply[Option[String]]($m, {
        final class $typecreator1 extends TypeCreator {
          def <init>(): $typecreator1 = {
            $typecreator1.super.<init>();
            ()
          };
          def apply[U <: scala.reflect.api.Universe with Singleton]($m$untyped: scala.reflect.api.Mirror[U]): U#Type = {
            val $u: U = $m$untyped.universe;
            val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror];
            $u.internal.reificationSupport.TypeRef($u.internal.reificationSupport.ThisType($m.staticPackage("scala").asModule.moduleClass), $m.staticClass("scala.Option"), scala.collection.immutable.List.apply[$u.Type]($u.internal.reificationSupport.TypeRef($u.internal.reificationSupport.SingleType($m.staticPackage("scala").asModule.moduleClass.asType.toTypeConstructor, $m.staticModule("scala.Predef")), $u.internal.reificationSupport.selectType($m.staticModule("scala.Predef").asModule.moduleClass, "String"), scala.collection.immutable.Nil)))
          }
        };
        new $typecreator1()
      })
    }: reflect.runtime.universe.TypeTag[Option[String]]));
```

A new TypeTag is created time `def materializeTag` is called above; the program prints `false`.

This commit introduces a cache, keyed by the synthetic `$typecreator1`, and hosted in the `JavaMirror`.
We know that the `apply` method is a pure, so the caching is sound.

Using `ClassValue` means that we're not introducing a classloader leak.

We are extending the lifetime of the `TypeTag` and contained type itself, which represents a small
risk to existing applications, so I've included an opt-out System property.
@retronym retronym force-pushed the topic/typetag-cache branch from 3ebf76d to 35501d9 Compare June 20, 2019 20:01
Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me, leaving open for others to take a look.

@eed3si9n eed3si9n merged commit 87a3dd7 into scala:2.12.x Jul 23, 2019
@eed3si9n
Copy link
Member

I'm not familiar with ClassValue, so I wasn't sure how weakly it holds on to the class references.

https://bugs.openjdk.java.net/browse/JDK-8136353?focusedCommentId=13922257&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-13922257 says

The ClassValue class uses a WeakHashmap to semantically map between classes and values. Instances of ClassValue$Identity are used as 'keys' and the Dummy instances are used as the 'values'. Since the 'values' in WeakHashMap are held strongly, the keys are also strongly reachable and will not be cleared.

so I thought it looks ok to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance the need for speed. usually compiler performance, sometimes runtime performance. release-notes worth highlighting in next release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants