Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 = {...}

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 = {...}

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
} )

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.

EDIT: 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
like image 945
paradigmatic Avatar asked Aug 23 '12 08:08

paradigmatic


People also ask

Is any way we can avoid the boxing and unboxing?

How to prevent boxing & unboxing: Use ToString method of numeric data types such as int, double, float etc. Use for loop to enumerate on value type arrays or lists (do not use foreach loop or LINQ queries) Use for loop to enumerate on characters of string (do not use foreach loop or LINQ queries)

What are the disadvantages of boxing unboxing?

Boxing is an expensive process, since it copies an object from a stack to a heap which requires a number of processor as well as space on the heap. Another disadvantage of using boxing is that the same object appears at two different places in memory which can have contradictory state.

Why boxing and unboxing is required?

Boxing and unboxing enables a unified view of the type system wherein a value of any type can ultimately be treated as an object. With Boxing and unboxing one can link between value-types and reference-types by allowing any value of a value-type to be converted to and from type object.

What is consequence of boxing and unboxing?

Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type. When the common language runtime (CLR) boxes a value type, it wraps the value inside a System. Object instance and stores it on the managed heap. Unboxing extracts the value type from the object.


2 Answers

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.

like image 71
Kim Stebel Avatar answered Oct 16 '22 21:10

Kim Stebel


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.

like image 26
axel22 Avatar answered Oct 16 '22 22:10

axel22