Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use Scala @specialized for a memory-efficient wrapper class around a primitive?

Tags:

scala

I have a wrapper class that can store a Long, Double, or Boolean primitive value (as well as some other stuff that I've removed for simplicity). My original naive implementation just stored the wrapped value in a field of type Any, which resulted in the values being boxed.

To remove the boxing and reduce memory usage, I tried using generics, but learned that due to type erasure, this doesn't save anything. So I tried using @specialized, but got surprising results.

The below code was built with scalac 2.9.3 and run on JDK7. MemoryMeasurer comes from here and I believe it to be accurate. The "padding" field isn't significant; I am just using it to pad the base object (without the wrapped value) to 16 bytes, so the effect of my various attempts is more clear.

import objectexplorer.MemoryMeasurer

class GenericNonSpecialized[A] (wrapped: A, val padding: Int) {
  def getWrapped: Any = wrapped
}

class GenericSpecialized[@specialized(Long, Double, Boolean) A] (wrapped: A, val padding: Int) {
  def getWrapped: A = wrapped
}

class GenericSpecializedVal[@specialized(Long, Double, Boolean) A] (val wrapped: A, val padding: Int) {
  def getWrapped: A = wrapped
}

class NonGeneric(val wrapped: Long, padding: Int) {
}

object App {
  def main(args: Array[String]) {
    println(MemoryMeasurer.measureBytes(new GenericNonSpecialized(4L, 0)))
    // Expect: 48: NonSpecialized object (24 bytes) + boxed long (24 bytes)
    // Actual: 48

    // I expect all of the below to be 24 bytes: Object overhead (12 bytes) + Long (8 bytes) + Int (4 bytes),
    // but only the non-generic one is actually 24 bytes.

    println(MemoryMeasurer.measureBytes(new GenericSpecialized(4L, 0))) // 56

    println(MemoryMeasurer.measureBytes(new GenericSpecializedVal(4L, 0))) // 32

    println(MemoryMeasurer.measureBytes(new NonGeneric(4L, 0))) // 24
  }
}

Questions:

  1. How do I create a generic wrapper object that uses 24 bytes, like the non-generic equivalent? (my best attempt, GenericSpecializedVal uses 32)
  2. Why does GenericNonSpecialized use 56 bytes, but if I add "val", making "wrapped" into an actual field it drops to 32 bytes?
like image 238
Michael Lehenbauer Avatar asked May 03 '13 17:05

Michael Lehenbauer


1 Answers

Unfortunately, specialized classes inherit from their non-specialized parent, and that one contains storage space for a boxed copy. The short answer therefore is that you can't form an efficient wrapper this way.

You can declare the data in a trait:

trait Boxer[@specialized A]{ def boxed: A }

and then provide implementations manually:

class BoxerInt(val boxed: Int) extends Boxer[Int]
class BoxerDouble(val boxed: Double) extends Boxer[Double]

and then write the Boxer companion to overload the apply method:

object Boxer {
  def apply(i: Int) = new BoxerInt(i)
  def apply(d: Double) = new BoxerDouble(d)
}

so that you can make it look like you didn't have to do all that work:

val box = Boxer(5.0)

but it still isn't completely seamless with other uses of specialization (in particular creation in a generic context will always be an issue).

like image 92
Rex Kerr Avatar answered Oct 17 '22 09:10

Rex Kerr