mariop
mariop

Reputation: 3226

Performance of typeclasses in Scala

I'm trying to analyse the cost in performance of using typeclasses in Scala because I noticed that when they are used extensively, performance tend to drop. Let's take, for instance, a ByteCodec typeclass:

trait ByteCodec[T] {
  def put(index: Int, byteBuffer: ByteBuffer, t: T): Unit
  def get(index: Int, byteBuffer: ByteBuffer): T
}

Let's then make a Long instance:

object ByteCodec {
  def apply[T](implicit bc: ByteCodec[T]): ByteCodec[T] = bc

  implicit val longBC = new ByteCodec[Long] {
    @inline override def put(index: Int, byteBuffer: ByteBuffer, long: Long): Unit = {
      val _ = byteBuffer.putLong(index, long)
    }

    @inline override def get(index: Int, byteBuffer: ByteBuffer): Long =
      byteBuffer.getLong(index)
  }
}

If I run 100 millions gets and puts, this takes ~1200ms for the typeclass test and ~800ms otherwise. Where is the overhead and can I get rid of it?

The code of the main:

object Main extends App {
  val cycles = 100000000
  val byteBuffer = ByteBuffer.allocate(java.lang.Long.BYTES)
  var start = System.currentTimeMillis()
  var currCycle = cycles
  while (currCycle > 0) {
    byteBuffer.putLong(0, 10L)
    val k = byteBuffer.getLong(0)
    currCycle -= 1
  }
  var end = System.currentTimeMillis()
  println(s"time elapsed byteBuffer ${ end - start }")

  val codec = ByteCodec[Long]
  start = System.currentTimeMillis()
  currCycle = cycles
  while (currCycle > 0) {
    codec.put(0, byteBuffer, 10L)
    val k = codec.get(0, byteBuffer)
    currCycle -= 1
  }
  end = System.currentTimeMillis()
  println(s"time elapsed ByteCodec ${ end - start }")
}

Upvotes: 3

Views: 270

Answers (1)

Giovanni Caporaletti
Giovanni Caporaletti

Reputation: 5556

Aleksey already mentioned in a comment one reason why your test is inaccurate.

Apart from that, the main reason why your typeclass is slower has nothing to do with the approach itself: it's the boxing/unboxing of Longs that makes it slower. You can specialize your typeclass for value classes using the @specialized annotation:

trait ByteCodec[@specialized(Long) T]

If you take a look at the signatures from ByteBuffer, value types are used and no boxing/unboxing is involved:

 public abstract ByteBuffer putLong(int index, long value);
 public abstract long getLong(int index);

Upvotes: 7

Related Questions