Avoiding boxing/unboxing within function

后端 未结 3 1835
[愿得一人]
[愿得一人] 2021-02-01 08:48

For a numeric intensive code I have written a function with the following signature:

def update( f: (Int,Int,Double) => Double ): Unit = {...}
相关标签:
3条回答
  • 2021-02-01 09:16

    About the anonymous class which extends Function3 (the bytecode of which you show) generated by Scalac - it is not possible to call the overloaded apply with primitive parameters from within the b.update, because that update method takes a Function3, which does not have an apply with primitive parameters.

    From within the Function3 bytecode, the only apply is:

    public abstract java.lang.Object apply(java.lang.Object, java.lang.Object, java.lang.Object);
    

    You could instead use Function2[Long, Double], which is specialized on those types, and encode 2 integers x and y in a long as (x.toLong << 32) + y, and decode them as v & 0xffffffff and (v >> 32) & 0xffffffff.

    0 讨论(0)
  • 2021-02-01 09:21

    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.

    The trait F3

    trait F3[@specialized A, @specialized B, @specialized C, @specialized D] {
      def apply(a:A, b:B, c:C):D
    }
    

    The macro

      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)
    

    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
    

    The only boxing that is done here is for the call to println. Yay!

    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.

    0 讨论(0)
  • 2021-02-01 09:26

    As Function1 is specialized, a possible solution is to use currying and change your update method to:

    def update( f: Int => Int => Double => Double ): Unit = {...}
    

    and change the inlined function accordingly. with your example (update was slightly modified to test it quickly):

    scala> def update( f: Int => Int => Double => Double ): Double = f(1)(2)(3.0)
    update: (f: Int => (Int => (Double => Double)))Double
    
    scala> update(i => j => _ => if (i == 0 && j == 0) 1.0 else 0.5)
    res1: Double = 0.5
    

    Edit: Ase explained in the comments, it doesn't completely help since the first parameter is still boxed. I leave the answer to keep a trace about it.

    0 讨论(0)
提交回复
热议问题