Уменьшение времени компиляции при использовании бесформенного HList - PullRequest
4 голосов
/ 21 мая 2019

Scala 2.12.6, Shapeless 2.3.3.

У меня много больших моделей (тематических классов), которые довольно глубоки.Я использую бесформенную форму, чтобы помочь использовать / манипулировать этими моделями, а также использую такие библиотеки, как circe, которые интенсивно используют бесформенную форму.

Это привело к значительному увеличению времени моей компиляции во время phase typer части скаляка.

Исходя из небольшого количества Google, кажется, что бесформенный виновник, но я не могу найти какие-либо конкретные советы о том, как я могу улучшить это.

Было высказано предположение, что, поскольку я решаюHList подразумевает несколько раз (из-за нескольких библиотек) для одних и тех же моделей, что я должен их «кэшировать» - однако я не уверен, как точно определить, что именно кэшировать.

Учитывая что-то вроде:

case class MyModel(value: String) extends AnyVal
case class MyOtherModel(value: Int) extends AnyVal
case class MyRootModel(myModel: MyModel, myOtherModel: MyOtherModel)

Что я должен кэшировать для MyModel / MyOtherModel и MyRootModel?

1 Ответ

9 голосов
/ 21 мая 2019

Вы можете кэшировать LabelledGeneric экземпляры Shapeless, например, так:

import shapeless.{LabelledGeneric, the}

case class MyModel(value: String) extends AnyVal
case class MyOtherModel(value: Int) extends AnyVal
case class MyRootModel(myModel: MyModel, myOtherModel: MyOtherModel)

object MyModel {
  implicit val generic = the[LabelledGeneric[MyModel]]
}

object MyOtherModel {
  implicit val generic = the[LabelledGeneric[MyOtherModel]]
}

object MyRootModel {
  implicit val generic = the[LabelledGeneric[MyRootModel]]
}

Конечно, вы должны посмотреть, улучшает ли это время компиляции в вашем собственном проекте, но в качестве быстрого теста мы можем настроить тест, которыйразрешает LabelledGeneric несколько раз (в данном случае тысячу раз):

object Test {
  def foo0 = {
    LabelledGeneric[MyRootModel]
    LabelledGeneric[MyRootModel]
    // repeat 98 more times...
  }
  def foo1 = {
    LabelledGeneric[MyRootModel]
    LabelledGeneric[MyRootModel]
    // repeat 98 more times...
  }
  // and so on through foo9 
}

(Обратите внимание, что мы должны разделить вызовы, потому что если мы просто сбросили тысячу из них в строке одним способомсгенерированный макросом будет превышать пределы размера метода JVM, когда мы закомментируем кеширование экземпляра для сравнения.)

На моем компьютере файл Test.scala, содержащий Test, определения класса case и кэшированныйэкземпляры компилируются примерно за 3 секунды.Если мы закомментируем определения generic, это займет более 12 секунд.Это, конечно, довольно ненаучно, но это обнадеживает.

Обратите внимание, что в общем случае не стоит иметь implicit определений без аннотации типа, и вы можете избежать этого для кэшированных экземпляров, написав что-товот так:

import shapeless.{LabelledGeneric, TypeOf, cachedImplicit}

case class MyModel(value: String) extends AnyVal
case class MyOtherModel(value: Int) extends AnyVal
case class MyRootModel(myModel: MyModel, myOtherModel: MyOtherModel)

object MyModel {
  implicit val generic: TypeOf.`LabelledGeneric[MyModel]`.type = cachedImplicit
}

object MyOtherModel {
  implicit val generic: TypeOf.`LabelledGeneric[MyOtherModel]`.type = cachedImplicit
}

object MyRootModel {
  implicit val generic: TypeOf.`LabelledGeneric[MyRootModel]`.type = cachedImplicit
}

TypeOf - это какая-то странная магия, и, честно говоря, когда мне нужно что-то подобное, я просто использовал подход the.

В качестве сноски, так как вы упомянули специально о Circe, вы можете попробовать Circe-Derivation .Он работает в качестве замены для большей части функциональных возможностей circe-generic, но не основан на Shapeless и компилируется намного быстрее.

...