避免在功能内装箱/拆箱 [英] Avoiding boxing/unboxing within function
问题描述
对于数字密集型代码,我编写了具有以下签名的函数:
For a numeric intensive code I have written a function with the following signature:
def update( f: (Int,Int,Double) => Double ): Unit = {...}
但是,由于Function3
不是专门的,因此f
的每个应用程序都会对3个参数和结果类型进行装箱/拆箱.
However, because Function3
is not specialized, every application of f
results in boxing/unboxing the 3 arguments and the result type.
我可以使用特殊的更新程序类:
I could use a special updater class:
trait Updater {
def apply( i: Int, j: Int, x: Double ): Double
}
def update( f: Updater ): Unit = {...}
但是调用很麻烦(和java-ish一样):
But the invocation is cumbersome (and java-ish):
//with function
b.update( (i,j,_) => if( i==0 || j ==0 ) 1.0 else 0.5 )
//with updater
b.update( new Updater {
def apply( i: Int, j: Int, x: Double ) = if( i==0 || j ==0 ) 1.0 else 0.5
} )
有没有办法在仍使用lambda语法的情况下避免装箱/拆箱?我希望宏会有所帮助,但我找不到任何解决方案.
Is there a way to avoid boxing/unboxing while still using the lambda syntax ? I was hoping macros will help, but I cannot figure any solution.
编辑:我用javap分析了function3生成的字节码.编译器会沿着通用方法生成未装箱的方法(请参见下文).有没有办法直接给拆箱的人打电话?
I analyzed the function3 generated byte code with javap. An unboxed method is generated by the compiler, along the generic method (see below). Is there a way to call the unboxed one directly ?
public final double apply(int, int, double);
Code:
0: ldc2_w #14; //double 100.0d
3: iload_2
4: i2d
5: dmul
6: iload_1
7: i2d
8: ddiv
9: dreturn
public final java.lang.Object apply(java.lang.Object, java.lang.Object, java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokestatic #31; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: aload_2
6: invokestatic #31; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
9: aload_3
10: invokestatic #35; //Method scala/runtime/BoxesRunTime.unboxToDouble:(Ljava/lang/Object;)D
13: invokevirtual #37; //Method apply:(IID)D
16: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
19: areturn
推荐答案
由于您提到宏是一种可能的解决方案,所以我想到了编写一个带有匿名函数的宏的想法,提取应用方法并将其插入到匿名中扩展了名为F3
的自定义函数特征的类.这是相当长的实现.
Since you mentioned macros as a possible solution, I got the idea of writing a macro that takes an anonymous function, extracts the apply methods and inserts it into an anonymous class that extends a custom function trait called F3
. This is the quite long implementation.
特质F3
trait F3[@specialized A, @specialized B, @specialized C, @specialized D] {
def apply(a:A, b:B, c:C):D
}
宏
implicit def function3toF3[A,B,C,D](f:Function3[A,B,C,D]):F3[A,B,C,D] = macro impl[A,B,C,D]
def impl[A,B,C,D](c:Context)(f:c.Expr[Function3[A,B,C,D]]):c.Expr[F3[A,B,C,D]] = {
import c.universe._
var Function(args,body) = f.tree
args = args.map(c.resetAllAttrs(_).asInstanceOf[ValDef])
body = c.resetAllAttrs(body)
val res =
Block(
List(
ClassDef(
Modifiers(Flag.FINAL),
newTypeName("$anon"),
List(),
Template(
List(
AppliedTypeTree(Ident(c.mirror.staticClass("mcro.F3")),
List(
Ident(c.mirror.staticClass("scala.Int")),
Ident(c.mirror.staticClass("scala.Int")),
Ident(c.mirror.staticClass("scala.Double")),
Ident(c.mirror.staticClass("scala.Double"))
)
)
),
emptyValDef,
List(
DefDef(
Modifiers(),
nme.CONSTRUCTOR,
List(),
List(
List()
),
TypeTree(),
Block(
List(
Apply(
Select(Super(This(newTypeName("")), newTypeName("")), newTermName("<init>")),
List()
)
),
Literal(Constant(()))
)
),
DefDef(
Modifiers(Flag.OVERRIDE),
newTermName("apply"),
List(),
List(args),
TypeTree(),
body
)
)
)
)
),
Apply(
Select(
New(
Ident(newTypeName("$anon"))
),
nme.CONSTRUCTOR
),
List()
)
)
c.Expr[F3[A,B,C,D]](res)
}
由于我将宏定义为隐式的,因此可以像这样使用它:
Since I defined the macro as implicit, it can be used like this:
def foo(f:F3[Int,Int,Double,Double]) = {
println(f.apply(1,2,3))
}
foo((a:Int,b:Int,c:Double)=>a+b+c)
在调用foo之前,将调用宏,因为foo
需要F3
的实例.正如预期的那样,对foo
的调用将显示"6.0".现在,让我们看一下foo
方法的反汇编,以确保没有装箱/拆箱:
Before foo is called, the macro is invoked because foo
expects an instance of F3
. As expected, the call to foo
prints "6.0". Now let's look at the disassembly of the foo
method, to make sure that no boxing/unboxing takes place:
public void foo(mcro.F3);
Code:
Stack=6, Locals=2, Args_size=2
0: getstatic #19; //Field scala/Predef$.MODULE$:Lscala/Predef$;
3: aload_1
4: iconst_1
5: iconst_2
6: ldc2_w #20; //double 3.0d
9: invokeinterface #27, 5; //InterfaceMethod mcro/F3.apply$mcIIDD$sp:(IID)D
14: invokestatic #33; //Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
17: invokevirtual #37; //Method scala/Predef$.println:(Ljava/lang/Object;)V
20: return
这里唯一的装箱是对println
的调用.是的!
The only boxing that is done here is for the call to println
. Yay!
最后一句话:在当前状态下,该宏仅适用于Int,Int,Double,Double
的特殊情况,但是可以很容易地对其进行修复.我把它留给读者练习.
One last remark: In its current state, the macro only works for the special case of Int,Int,Double,Double
but that can easily be fixed. I leave that as an exercise to the reader.
这篇关于避免在功能内装箱/拆箱的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!