Skip to content

Annotations.labelOf is catastrophically slow #288

@timw

Description

@timw

#253 introduced in Annotations.labelOf[T] an implementation of extracting the @label value that worked for non literal values (e.g. @label(SomeClass.LABEL) as well as literal values (e.g. @label("vert")).

This implementation, unfortunately, is terribly slow (to the point I noticed a single invocation of the method slowed a unit test by over a second, which got me to investigating).

The labelOf implementation uses Toolbox.eval, which invokes the back-end of the Scala compiler.
This implementation averages about 65ms per invocation on my dev system (an i7 MBP).

An implementation that only uses the introspection APIs is 1000 times quicker (something like 60 micro seconds):

  def fastLabelOf[T <: Product: WeakTypeTag]: Option[String] = {
    weakTypeOf[T].typeSymbol.asClass.annotations
      .find(_.tree.tpe =:= typeOf[label])
      .map( _.tree.children.tail.head match { case Literal(Constant(id: String)) => id })
  }

This is in essence what the Marshallable materialization macro and the original code does.
The problem is obviously that this regresses the ability to retrieve a non literal value.

A macro based implementation is 1,000,000 times quicker:

  def labelOf[T]: Option[label] = macro labelOfImpl[T]

  def labelOfImpl[T: c.WeakTypeTag](c: Context): c.Expr[Option[label]] = {
    import c.universe._
    val tpe = weakTypeOf[T]

    val label = tpe.typeSymbol.asClass.annotations
      .find(_.tree.tpe =:= weakTypeOf[label])
      .map { annotation => q"""Some(new label(${annotation.tree.children.tail.head}))"""}
      .getOrElse(q"None")
    c.Expr[Option[label]](label)
  }

(This is keeping the labelOf[T]: Option[label] contract - in actual use, we only need the String value).

Unfortunately this has another problem, in that it needs a WeakTypeTag of a real class type, which can only be produced at the time of invocation.
e.g. Calling Annotations.labelOf[MyType] will work, but not Annotations.labelOf[T] as we see in GremlinScala.hasLabel[CC]. The WeakTypeTag that is passed through hasLabel isn't compatible with the implicit macro context WeakTypeTag required by the macro.

At this point I'm not sure what the best resolution is (otherwise I'd have PRed this):

  • There may be some Scala macro fu I don't know of to adapt the WeakTypeTag so it can be invoked indirectly through methods like hasLabel.
  • Alternatively the API style for case class use in GremlinScala and ScalaGraph could change to take an implicit LabelExtractor implementation materialized by the macro (i.e. instead of the implicit WeakTypeTag evidence provided by the type context bound now). I have this prototyped, and it works.

I'm happy to put together a PR if there's a consensus on a solution, but I can't think of anything at the moment that doesn't break API and/or ABI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions