Я ищу хорошие практики, чтобы избежать повторного переписывания одного и того же кода снова и снова, чтобы добиться бесполезности. Скажем, у меня есть что-то вроде этого:Boilerplate Free Scala ArrayBuilder специализация
def speedyArrayMaker[@specialized(Long) A: ClassTag](...): Array[A] = {
val builder = Array.newBuilder[A]
// do stuff with builder
builder.result
}
Это приведет к Unboxed хранения, лежащий в основе моего builder
, когда это возможно, но, как я понимаю, не распакованный метод не вызывает к нему, потому что я иду через неспециализированной ArrayBuilder
черты.
В мономорфной мире специализируется на Long
, я бы написать val builder = new ArrayBuilder.ofLong()
и избежать бокс на всех, но мало того, чтобы говорить ArrayBuilder
/Builder
специализироваться на всех примитивных типов, я не могу думать хороший способ, чтобы избежать дублирования усилие здесь. Один подход, который я думал может быть, в speedyArrayMaker
:
val (add, builder): (A => Unit, ArrayBuilder[A]) = implicitly[ClassTag[A]].runtimeClass match {
case java.lang.Long.TYPE =>
val builder = new ArrayBuilder.ofLong()
((x: Long) => builder += x, builder).asInstanceOf
case _ =>
val builder = Array.newBuilder[A]
((x: A) => builder += x, builder)
}
Поскольку это только +=
метод мы действительно заботимся, чтобы получить специализированные, а затем мы получаем Function1
для add
, которая специализируется на Long
. Проверка с помощью javap, на самом деле я получаю
90: invokestatic #118; //Method scala/runtime/BoxesRunTime.boxToLong:(J)Ljava/lang/Long;
93: invokeinterface #127, 2; //InterfaceMethod scala/collection/mutable/Builder.$plus$eq:(Ljava/lang/Object;)Lscala/collection/mutable/Builder;
для версии Array.newBuilder[A]
(даже в специализированной продукции), а также:
252: invokeinterface #204, 3; //InterfaceMethod scala/Function1.apply$mcVJ$sp:(J)V
для свернутой версии. Я могу помещать этот шаблон в функцию «специализированного помощника строителя», но он кажется уродливым, особенно когда он все еще отправляется во время выполнения на основе чего-то известного во время компиляции во время специализации. В конечном итоге я бы сказал, что мое предложение здесь заключается в том, чтобы контактировать с тем, что Function1
уже специализирован, и мне это не особенно нравится.
Есть ли умные трюки, которые я могу использовать, чтобы сделать это более приятным? Я понимаю, что это очень низкоуровневая деталь и редко будет критичной по производительности, но с учетом количества усилий/дублирования кода, которые попадают во все специализированные классы ArrayBuilder.of*
, кажется, что очень жаль отбросить некоторые из их преимуществ в обмен на будучи полиморфными.
Редактировать Я думал о чем-то уродливым, но я надеялся, что будет работать:
def builderOf(x: Array[Int]): ArrayBuilder.ofInt = new ArrayBuilder.ofInt()
def builderOf(x: Array[Long]): ArrayBuilder.ofLong = new ArrayBuilder.ofLong()
def builderOf[A: ClassTag](x: Array[A]): ArrayBuilder[A] = ArrayBuilder.make[A]
, а затем внутри моей специализированной функции:
val witness: Array[A] = null
val builder = builderOf(witness)
, но это, кажется, называют родовое builderOf
даже в специализированной версии (хотя достаточно информации о типе доступно для вызова версии Array[Long]
). Кто-нибудь знает, почему это не работает? Подход кажется довольно чистым, по сравнению с другим, который я предлагал. Я предполагаю, что я надеялся на более «макроподобный» подход к специализации, но я думаю, что нет никакой гарантии, что он будет корректным для всех экземпляров, если только он не выбирает один и тот же метод для каждой специализации :(
Я не уверен, чтобы увидеть, как все, что вы будете делать, вы можете получить любую специализацию, учитывая, что 'ArrayBuidler' не специализируется (и, таким образом,' + = 'никогда не будет специализироваться даже при вызове из специализированного метода).Вы получите специализацию только в том случае, если вы просто обходите «ArrayBuidler» (например, определив свою собственную специализированную версию). –
На самом деле, мне показалось, что специализация «всего лишь» внешнего метода (тот, который вызывает «+ =»), может уже нам значительно ускорить, позволяя джиттеру выполнять вставку с мономорфным кешем. Это то, что вы имели в виду? –
Моя точка (это моя другая учетная запись) заключается в том, что существует множество специализированных подклассов ArrayBuilder под названием 'ofInt',' ofDouble' и т. Д. Они используются, когда вы запрашиваете 'Array.newBuilder [someprimitive]', но вы могут также создавать их непосредственно. Если вы используете 'newBuilder', вы получаете' ArrayBuilder', который не является специализированным, но если вы создаете экземпляр 'new ArrayBuilder.ofInt()', вы также получите unboxed-вызовы на '+ =', и это то, что я пытался для захвата выше. Вы можете протестировать это, аннотируя 'new ofInt()' с более конкретными и менее конкретными типами и посмотреть, есть ли у вас бокс-вызов. – copumpkin