Does Inheritance in implicit value classes introduce an overhead?
Asked Answered
P

1

9

I want to apply scala's value classes to one of my projects because they enable me to enrich certain primitive types without great overhead (I hope) and stay type-safe.

object Position {

  implicit class Pos( val i: Int ) extends AnyVal with Ordered[Pos] {

    def +( p: Pos ): Pos = i + p.i

    def -( p: Pos ): Pos = if ( i - p.i < 0 ) 0 else i - p.i

    def compare( p: Pos ): Int = i - p.i

  }
}

My question: Will the inheritance of Ordered force the allocation of Pos objects whenever I use them (thereby introduce great overhead) or not? If so: Is there a way to circumvent this?

Pappano answered 14/5, 2013 at 9:25 Comment(0)
D
6

Everytime Pos will be treated as an Ordered[Pos], allocation will happen. There are several cases when allocation has to happen, see http://docs.scala-lang.org/overviews/core/value-classes.html#when_allocation_is_necessary.

So when doing something as simple as calling <, you will get allocations:

val x = Pos( 1 )
val y = Pos( 2 )
x < y // x & y promoted to an actual instance (allocation)

The relevant rules are (quoted from the above article):

Whenever a value class is treated as another type, including a universal trait, an instance of the actual value class must be instantiated and: Another instance of this rule is when a value class is used as a type argument.

Disassembling the above code snippet confirms this:

 0: aload_0
 1: iconst_1
 2: invokevirtual #21                 // Method Pos:(I)I
 5: istore_1
 6: aload_0
 7: iconst_2
 8: invokevirtual #21                 // Method Pos:(I)I
11: istore_2
12: new           #23                 // class test/Position$Pos
15: dup
16: iload_1
17: invokespecial #26                 // Method test/Position$Pos."<init>":(I)V
20: new           #23                 // class test/Position$Pos
23: dup
24: iload_2
25: invokespecial #26                 // Method test/Position$Pos."<init>":(I)V
28: invokeinterface #32,  2           // InterfaceMethod scala/math/Ordered.$less:(Ljava/lang/Object;)Z

As can be seen we do have two instances of the "new" opcode for class Position$Pos

UPDATE: to avoid the allocation in simples cases like this, you can manually override each method (even if they only forward to the originlal implementation):

override def <  (that: Pos): Boolean = super.<(that)
override def >  (that: Pos): Boolean = super.>(that)
override def <= (that: Pos): Boolean = super.<=(that)
override def >= (that: Pos): Boolean = super.>=(that)

This will remove the allocation when doing x < y by example. However, this still leaves the cases when Pos is treated as an Ordered[Pos] (as when passed to a method taking a Ordered[Pos] or an Ordered[T] with T being a type parameter). In this particular case, you will still get an allocation and there no way around that.

Drona answered 14/5, 2013 at 13:28 Comment(1)
You know of any way to avoid the allocation an make Pos comparable?Pappano

© 2022 - 2024 — McMap. All rights reserved.