Scala 避免在函数中装箱/拆箱
对于数字密集型代码,我编写了一个具有以下签名的函数:Scala 避免在函数中装箱/拆箱,scala,macros,boxing,Scala,Macros,Boxing,对于数字密集型代码,我编写了一个具有以下签名的函数: def update( f: (Int,Int,Double) => Double ): Unit = {...} 但是,由于Function3不是专门化的,因此f的每个应用程序都会导致装箱/取消装箱3个参数和结果类型 我可以使用一个特殊的更新程序类: trait Updater { def apply( i: Int, j: Int, x: Double ): Double } def update( f: Updater ):
def update( f: (Int,Int,Double) => Double ): Unit = {...}
但是,由于Function3
不是专门化的,因此f
的每个应用程序都会导致装箱/取消装箱3个参数和结果类型
我可以使用一个特殊的更新程序类:
trait Updater {
def apply( i: Int, j: Int, x: Double ): Double
}
def update( f: Updater ): Unit = {...}
但是调用很麻烦(而且是java语言):
在仍然使用lambda语法的情况下,有没有办法避免装箱/取消装箱?我希望宏能有所帮助,但我想不出任何解决方案
编辑:我用javap分析了function3生成的字节码。未绑定的方法由编译器生成,与泛型方法一起生成(见下文)。有没有办法直接调用未绑定的
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
关于扩展Scalac生成的
Function3
(您显示的字节码)的匿名类-无法从b.update
中使用原语参数调用重载的apply
,因为update
方法采用Function3
,没有带基本参数的应用
在功能3
字节码中,唯一的应用
是:
public abstract java.lang.Object apply(java.lang.Object, java.lang.Object, java.lang.Object);
您可以改为使用专门针对这些类型的
Function2[Long,Double]
,并在(x.toLong>32)&0xffffffff
中编码2个整数x
和y
,因为Function1
是专门的,一个可能的解决方案是使用currying并将更新方法更改为:
def update( f: Int => Int => Double => Double ): Unit = {...}
并相应地更改内联函数。对于您的示例(update
稍微修改以快速测试):
编辑:如注释中所述,由于第一个参数仍处于装箱状态,因此它没有完全起作用。我留下答案来跟踪它。既然您提到了宏作为一种可能的解决方案,我就想到了编写一个宏,它接受一个匿名函数,提取apply方法并将其插入一个匿名类,该类扩展了一个名为
F3
的自定义函数特性。这是一个相当长的实现
特征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)
}
在调用foo之前,会调用宏,因为foo
需要一个F3
的实例。正如预期的那样,对foo
的调用将打印“6.0”。现在让我们看看foo
方法的反汇编,以确保不会发生装箱/拆箱:
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
。耶
最后一句话:在当前状态下,宏仅适用于
Int,Int,Double,Double
的特殊情况,但这很容易修复。我把它作为一个练习留给读者。如何声明“apply(I:Int,j:Int,x:Double)=if(I==0 | | j==0)1.0 else 0.5”?有趣的是,我反编译了update方法,它仍然将第一个Int参数装箱以获得下一个函数。这是因为第一个函数的返回类型不是基元类型,而是对象,因此,函数不是专门化的。关于@specialized的另一件事是,一个专门化版本的Function3为25kb字节码生成700多个类文件。@jwinandy在这种情况下,我只需要一个(Int,Int,Double,Double)专门化,而不是所有的组合。建议不错。但是我仍然需要依靠boiler plate代码来手动转换/取消插入所有内容。函数3的专用版本的优先级为25kb字节码生成700多个类文件。您的宏是解决此类问题的一个很好的开端,但是您可以删除@specialized。您可以通过指定trait应专门化的类型来减少生成的类的数量。如果仅为Int
和double
,则只能得到2^^4=16个类文件。此外,这些额外的类文件不会以任何方式伤害您,因为它们从未加载。您如何删除@specialized?避免拳击是关键。这只是一个想法,我必须测试一下。既然你使用了覆盖,我想你不需要@specialized来避免拳击,也许我错了。那减少@specialized的范围呢?例如,在。。。
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)
}
def foo(f:F3[Int,Int,Double,Double]) = {
println(f.apply(1,2,3))
}
foo((a:Int,b:Int,c:Double)=>a+b+c)
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