MANUAL SPECIALIZATION
Szymon Matejczyk
@szymonmatejczyk
ACT 1:
WORLD RUINED
HIPSTERVS. OLDSCHOOL
def hipsterMin(a: Array[Int]): Int = {

a.foldLeft(Int.MaxValue){ _.min(_)}

}
def oldschoolMin(a: Array[Int]): Int = {

var min = Int.MaxValue

var i = 0

while (i < a.length) {

min = if (a(i) < min) a(i) else min

i += 1

}

min

}
HIPSTERVS. OLDSCHOOL
def hipsterMin(a: Array[Int]): Int = {

a.foldLeft(Int.MaxValue){ _.min(_)}

}
def oldschoolMin(a: Array[Int]): Int = {

var min = Int.MaxValue

var i = 0

while (i < a.length) {

min = if (a(i) < min) a(i) else min

i += 1

}

min

}
Running time
967722
123424
ACT 1I:
DENIAL
OLDSCHOOL
public int oldschoolMin(int[]);

Code:

0: ldc #72 // int 2147483647

2: istore_2

3: iconst_0

4: istore_3

5: iload_3

6: aload_1

7: arraylength

8: if_icmpge 33

11: aload_1

12: iload_3

13: iaload

14: iload_2

15: if_icmpge 24

18: aload_1

19: iload_3

20: iaload

21: goto 25

24: iload_2

25: istore_2

26: iload_3

27: iconst_1

28: iadd

29: istore_3

30: goto 5

33: iload_2

34: ireturn
HIPSTER
public int hipsterMin(int[]);

Code:

0: getstatic #67 // Field scala/Predef$.MODULE
$:Lscala/Predef$;

3: aload_1

4: invokevirtual #71 // Method scala/Predef
$.intArrayOps:([I)Lscala/collection/mutable/ArrayOps;

7: ldc #72 // int 2147483647

9: invokestatic #78 // Method scala/runtime/
BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;

12: new #80 // class szymon/Test$$anonfun
$hipsterMin$1

15: dup

16: aload_0

17: invokespecial #83 // Method szymon/Test$$anonfun
$hipsterMin$1."<init>":(Lszymon/Test;)V

20: invokeinterface #89, 3 // InterfaceMethod scala/
collection/mutable/ArrayOps.foldLeft:(Ljava/lang/Object;Lscala/
Function2;)Ljava/lang/Object;

25: invokestatic #93 // Method scala/runtime/
BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I

28: ireturn
HIPSTER
public final class szymon.Test$$anonfun$hipsterMin$1 extends scala.runtime.AbstractFunction2$mcIII$sp
implements scala.Serializable {

public static final long serialVersionUID;



public final int apply(int, int);

Code:

0: aload_0

1: iload_1

2: iload_2

3: invokevirtual #21 // Method apply$mcIII$sp:(II)I

6: ireturn



public int apply$mcIII$sp(int, int);

Code:

0: getstatic #32 // Field scala/runtime/RichInt$.MODULE$:Lscala/runtime/RichInt$;

3: getstatic #37 // Field scala/Predef$.MODULE$:Lscala/Predef$;

6: iload_1

7: invokevirtual #41 // Method scala/Predef$.intWrapper:(I)I

10: iload_2

11: invokevirtual #44 // Method scala/runtime/RichInt$.min$extension:(II)I

14: ireturn



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

Code:

0: aload_0

1: aload_1

2: invokestatic #51 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/
Object;)I

5: aload_2

6: invokestatic #51 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/
Object;)I

9: invokevirtual #53 // Method apply:(II)I

12: invokestatic #57 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/
Integer;

15: areturn



public szymon.Test$$anonfun$hipsterMin$1(szymon.Test);

Code:

0: aload_0

1: invokespecial #65 // Method scala/runtime/AbstractFunction2$mcIII$sp."<init>":()V

4: return
BOXING


public int apply$mcIII$sp(int, int);

Code:

0: getstatic #32 // Field scala/runtime/RichInt
$.MODULE$:Lscala/runtime/RichInt$;

3: getstatic #37 // Field scala/Predef$.MODULE
$:Lscala/Predef$;

6: iload_1

7: invokevirtual #41 // Method scala/Predef
$.intWrapper:(I)I

10: iload_2

11: invokevirtual #44 // Method scala/runtime/RichInt
$.min$extension:(II)I

14: ireturn



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

Code:

0: aload_0

1: aload_1

2: invokestatic #51 // Method scala/runtime/
BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I

5: aload_2

6: invokestatic #51 // Method scala/runtime/
BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I

9: invokevirtual #53 // Method apply:(II)I

12: invokestatic #57 // Method scala/runtime/
BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;

15: areturn
BOXING: HOWTO AVOID
• don’t use generics!
• use libraries for high performance code
• specialization?!
WHY SPECIALIZATION FAILS?
STEP BACKTO JAVA
• Trove
• fastutil
• HPPC
import scala.collection.JavaConversions._
def newInt2IntOpenHashMap(): mutable.Map[Int, Int] =

new Int2IntOpenHashMap().asInstanceOf[jutil.Map[Int, Int]]
STEP BACKTO JAVA
• Trove
• fastutil
• HPPC
import scala.collection.JavaConversions._
def newInt2IntOpenHashMap(): mutable.Map[Int, Int] =

new Int2IntOpenHashMap().asInstanceOf[jutil.Map[Int, Int]]
ACT 1II:
SURVIVE IN BROKEN WORLD
SPECIALIZED QUEUE
trait CQueue[@specialized(Int, Long) T] {

/**

* @return Next element to be dequeued.

*/

def first(): T



/**

*

* @return Next element that is removed from the `CQueue`.

*/

def deque(): T



/**

* Adds element to the `CQueue`.

*/

def +=(elem: T)



def isEmpty: Boolean

}
IMPLEMENTATION
class CQueueAny[T, QueueType <: PriorityQueue[T]](protected val
underlying: QueueType)

extends CQueue[T] {

def +=(elem: T): Unit = underlying.enqueue(elem)

def deque(): T = underlying.dequeue()

override def first(): T = underlying.first()

override def isEmpty: Boolean = underlying.isEmpty

}



class CQueueInt[QueueType <: IntPriorityQueue](protected val
underlying: QueueType)

extends CQueue[Int] {

override def +=(elem: Int): Unit = underlying.enqueue(elem)

override def deque(): Int = underlying.dequeueInt()

override def first(): Int = underlying.firstInt()

override def isEmpty: Boolean = underlying.isEmpty

}
FACTORY
abstract class CQueueFactory[T] {

def fifo(): FIFO[T]

def priority(): CQueue[T]

def priority(order: Order[T]): CQueue[T]

}
private[collections] class CQueueFactoryAny[T] extends CQueueFactory[T] {

def fifo(): FIFO[T] = new CQueueAny[T, ObjectArrayFIFOQueue[T]](

new ObjectArrayFIFOQueue[T]()) with FIFO[T] {

override def enqueueFirst(elem: T): Unit =
underlying.enqueueFirst(elem)

}

}



private[collections] class CQueueFactoryInt extends CQueueFactory[Int] {

override def fifo() = new CQueueInt[IntArrayFIFOQueue](
new IntArrayFIFOQueue()) with FIFO[Int] {

def enqueueFirst(elem: Int): Unit = underlying.enqueueFirst(elem)

}

}
GLUE
object CQueue {

import Implicits._



def fifo[@specialized(Int, Long) T](): FIFO[T]= {

implicitly[CQueueFactory[T]].fifo()

}



trait LowerPriorityImplicit {

private val factoryAny = new CQueueFactoryAny[AnyRef]()

implicit def factoryAny[T]: CQueueFactory[T] =
factoryAny.asInstanceOf[CQueueFactory[T]]

}



trait LowPriorityImplicit extends LowerPriorityImplicit {

implicit val factoryInt: CQueueFactory[Int] = new CQueueFactoryInt()

}



object Implicits extends LowPriorityImplicit

}
USAGEclass CQueueSpec extends WordSpec with Matchers {

def verifyQueue(q: CQueue[Int], dequeSeq: Seq[Int]): Unit = {

q.isEmpty should be (true)



q += 1

q += 2

q += 1

q += 3



q.isEmpty should be (false)



dequeSeq.foreach {

e => q.deque() should equal (e)

}

q.isEmpty should be (true)



intercept[NoSuchElementException](q.deque())

}



"CQueue" should {

"enqueue/deque elements" when {

"using fifo" in {

val q = CQueue.fifo[Int]()

verifyQueue(q, Seq(1, 2, 1, 3))

}

}

}

}
ACT IV:
MACROS!!
REKLAMY

Manual specialization

  • 1.
  • 2.
  • 3.
    HIPSTERVS. OLDSCHOOL def hipsterMin(a:Array[Int]): Int = {
 a.foldLeft(Int.MaxValue){ _.min(_)}
 } def oldschoolMin(a: Array[Int]): Int = {
 var min = Int.MaxValue
 var i = 0
 while (i < a.length) {
 min = if (a(i) < min) a(i) else min
 i += 1
 }
 min
 }
  • 4.
    HIPSTERVS. OLDSCHOOL def hipsterMin(a:Array[Int]): Int = {
 a.foldLeft(Int.MaxValue){ _.min(_)}
 } def oldschoolMin(a: Array[Int]): Int = {
 var min = Int.MaxValue
 var i = 0
 while (i < a.length) {
 min = if (a(i) < min) a(i) else min
 i += 1
 }
 min
 } Running time 967722 123424
  • 6.
  • 7.
    OLDSCHOOL public int oldschoolMin(int[]);
 Code:
 0:ldc #72 // int 2147483647
 2: istore_2
 3: iconst_0
 4: istore_3
 5: iload_3
 6: aload_1
 7: arraylength
 8: if_icmpge 33
 11: aload_1
 12: iload_3
 13: iaload
 14: iload_2
 15: if_icmpge 24
 18: aload_1
 19: iload_3
 20: iaload
 21: goto 25
 24: iload_2
 25: istore_2
 26: iload_3
 27: iconst_1
 28: iadd
 29: istore_3
 30: goto 5
 33: iload_2
 34: ireturn
  • 8.
    HIPSTER public int hipsterMin(int[]);
 Code:
 0:getstatic #67 // Field scala/Predef$.MODULE $:Lscala/Predef$;
 3: aload_1
 4: invokevirtual #71 // Method scala/Predef $.intArrayOps:([I)Lscala/collection/mutable/ArrayOps;
 7: ldc #72 // int 2147483647
 9: invokestatic #78 // Method scala/runtime/ BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
 12: new #80 // class szymon/Test$$anonfun $hipsterMin$1
 15: dup
 16: aload_0
 17: invokespecial #83 // Method szymon/Test$$anonfun $hipsterMin$1."<init>":(Lszymon/Test;)V
 20: invokeinterface #89, 3 // InterfaceMethod scala/ collection/mutable/ArrayOps.foldLeft:(Ljava/lang/Object;Lscala/ Function2;)Ljava/lang/Object;
 25: invokestatic #93 // Method scala/runtime/ BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
 28: ireturn
  • 9.
    HIPSTER public final classszymon.Test$$anonfun$hipsterMin$1 extends scala.runtime.AbstractFunction2$mcIII$sp implements scala.Serializable {
 public static final long serialVersionUID;
 
 public final int apply(int, int);
 Code:
 0: aload_0
 1: iload_1
 2: iload_2
 3: invokevirtual #21 // Method apply$mcIII$sp:(II)I
 6: ireturn
 
 public int apply$mcIII$sp(int, int);
 Code:
 0: getstatic #32 // Field scala/runtime/RichInt$.MODULE$:Lscala/runtime/RichInt$;
 3: getstatic #37 // Field scala/Predef$.MODULE$:Lscala/Predef$;
 6: iload_1
 7: invokevirtual #41 // Method scala/Predef$.intWrapper:(I)I
 10: iload_2
 11: invokevirtual #44 // Method scala/runtime/RichInt$.min$extension:(II)I
 14: ireturn
 
 public final java.lang.Object apply(java.lang.Object, java.lang.Object);
 Code:
 0: aload_0
 1: aload_1
 2: invokestatic #51 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/ Object;)I
 5: aload_2
 6: invokestatic #51 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/ Object;)I
 9: invokevirtual #53 // Method apply:(II)I
 12: invokestatic #57 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/ Integer;
 15: areturn
 
 public szymon.Test$$anonfun$hipsterMin$1(szymon.Test);
 Code:
 0: aload_0
 1: invokespecial #65 // Method scala/runtime/AbstractFunction2$mcIII$sp."<init>":()V
 4: return
  • 10.
    BOXING 
 public int apply$mcIII$sp(int,int);
 Code:
 0: getstatic #32 // Field scala/runtime/RichInt $.MODULE$:Lscala/runtime/RichInt$;
 3: getstatic #37 // Field scala/Predef$.MODULE $:Lscala/Predef$;
 6: iload_1
 7: invokevirtual #41 // Method scala/Predef $.intWrapper:(I)I
 10: iload_2
 11: invokevirtual #44 // Method scala/runtime/RichInt $.min$extension:(II)I
 14: ireturn
 
 public final java.lang.Object apply(java.lang.Object, java.lang.Object);
 Code:
 0: aload_0
 1: aload_1
 2: invokestatic #51 // Method scala/runtime/ BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
 5: aload_2
 6: invokestatic #51 // Method scala/runtime/ BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
 9: invokevirtual #53 // Method apply:(II)I
 12: invokestatic #57 // Method scala/runtime/ BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
 15: areturn
  • 11.
    BOXING: HOWTO AVOID •don’t use generics! • use libraries for high performance code • specialization?!
  • 12.
  • 13.
    STEP BACKTO JAVA •Trove • fastutil • HPPC import scala.collection.JavaConversions._ def newInt2IntOpenHashMap(): mutable.Map[Int, Int] =
 new Int2IntOpenHashMap().asInstanceOf[jutil.Map[Int, Int]]
  • 14.
    STEP BACKTO JAVA •Trove • fastutil • HPPC import scala.collection.JavaConversions._ def newInt2IntOpenHashMap(): mutable.Map[Int, Int] =
 new Int2IntOpenHashMap().asInstanceOf[jutil.Map[Int, Int]]
  • 15.
    ACT 1II: SURVIVE INBROKEN WORLD
  • 16.
    SPECIALIZED QUEUE trait CQueue[@specialized(Int,Long) T] {
 /**
 * @return Next element to be dequeued.
 */
 def first(): T
 
 /**
 *
 * @return Next element that is removed from the `CQueue`.
 */
 def deque(): T
 
 /**
 * Adds element to the `CQueue`.
 */
 def +=(elem: T)
 
 def isEmpty: Boolean
 }
  • 17.
    IMPLEMENTATION class CQueueAny[T, QueueType<: PriorityQueue[T]](protected val underlying: QueueType)
 extends CQueue[T] {
 def +=(elem: T): Unit = underlying.enqueue(elem)
 def deque(): T = underlying.dequeue()
 override def first(): T = underlying.first()
 override def isEmpty: Boolean = underlying.isEmpty
 }
 
 class CQueueInt[QueueType <: IntPriorityQueue](protected val underlying: QueueType)
 extends CQueue[Int] {
 override def +=(elem: Int): Unit = underlying.enqueue(elem)
 override def deque(): Int = underlying.dequeueInt()
 override def first(): Int = underlying.firstInt()
 override def isEmpty: Boolean = underlying.isEmpty
 }
  • 18.
    FACTORY abstract class CQueueFactory[T]{
 def fifo(): FIFO[T]
 def priority(): CQueue[T]
 def priority(order: Order[T]): CQueue[T]
 } private[collections] class CQueueFactoryAny[T] extends CQueueFactory[T] {
 def fifo(): FIFO[T] = new CQueueAny[T, ObjectArrayFIFOQueue[T]](
 new ObjectArrayFIFOQueue[T]()) with FIFO[T] {
 override def enqueueFirst(elem: T): Unit = underlying.enqueueFirst(elem)
 }
 }
 
 private[collections] class CQueueFactoryInt extends CQueueFactory[Int] {
 override def fifo() = new CQueueInt[IntArrayFIFOQueue]( new IntArrayFIFOQueue()) with FIFO[Int] {
 def enqueueFirst(elem: Int): Unit = underlying.enqueueFirst(elem)
 }
 }
  • 19.
    GLUE object CQueue {
 importImplicits._
 
 def fifo[@specialized(Int, Long) T](): FIFO[T]= {
 implicitly[CQueueFactory[T]].fifo()
 }
 
 trait LowerPriorityImplicit {
 private val factoryAny = new CQueueFactoryAny[AnyRef]()
 implicit def factoryAny[T]: CQueueFactory[T] = factoryAny.asInstanceOf[CQueueFactory[T]]
 }
 
 trait LowPriorityImplicit extends LowerPriorityImplicit {
 implicit val factoryInt: CQueueFactory[Int] = new CQueueFactoryInt()
 }
 
 object Implicits extends LowPriorityImplicit
 }
  • 20.
    USAGEclass CQueueSpec extendsWordSpec with Matchers {
 def verifyQueue(q: CQueue[Int], dequeSeq: Seq[Int]): Unit = {
 q.isEmpty should be (true)
 
 q += 1
 q += 2
 q += 1
 q += 3
 
 q.isEmpty should be (false)
 
 dequeSeq.foreach {
 e => q.deque() should equal (e)
 }
 q.isEmpty should be (true)
 
 intercept[NoSuchElementException](q.deque())
 }
 
 "CQueue" should {
 "enqueue/deque elements" when {
 "using fifo" in {
 val q = CQueue.fifo[Int]()
 verifyQueue(q, Seq(1, 2, 1, 3))
 }
 }
 }
 }
  • 21.
  • 22.