Overhead of "boxing" primitive types via implicits in Scala
Suppose I want to have a class like Java Date
. Its only data member is a long which represents the milliseconds since 1970.
Would/Could it be of any performance benefit of just making a new Scala type:
type PrimitiveDate = Long
Then you can add methods by using implicit conversion, like it is done for int with RichInt
. Does this "boxing" of the primitive type into a rich class involve any overhead (class creation)? Basically you could just have a static method
def addMonth(date: PrimitiveDate, months: Int): PrimitiveDate = date + 2592000000 * months
and let the type system figure out that it has to be applied when d addMonth 5
appears inside your code.
Edit
It seems that the alias you create by writing type PrimitiveDate = Long
is not enforced by the scala 开发者_开发问答compiler. Is creating a proper class, enclosing the Long, the only way to create an enforced type in Scala?
How useful do you consider being able to create an enforced type alias for primitive types?
Well, escape analysis should mean that the most recent JVMs do not actually have to create your rich wrapper in order to call the addMonth
method.
The extent to which this actually occurs in practice will obviously depend on how much of a runtime hotspot the JVM decides that these methods are adding in object creation. When escape analysis is not happening, obviously the JVM will have to "box" (as you say) the Long
in a new instance of the wrapper class. It doesn't involve "class creation" - it would involve "creating an instance of a class". This instance, being short-lived would then be GC-d immediately, so the overhead (whilst small) is:
- memory allocation for the instance
- GC-ing the instance
These are obviously only going to be any kind of problem if you are definitely writing very low-latency code where you are trying to minimize garbage creation (in a tight loop). Only you know whether this is the case.
As for whether the approach will work for you (and escape analysis come to your aid), you'd have to test in the wild. Micro-benchmarks are notoriously difficult to write for this kind of thing.
The reason I don't quite like these type aliases being part of a public API is that scala does not really enforce them as strictly as I would like. For example:
type PrimitiveDate = Long
type PrimitiveSpeed = Long
type Car = String
type Meeting = String
var maxSpeeds : Map[Car, PrimitiveSpeed] = Map.empty
//OOPS - much too easy to accidentally send the wrong type
def setDate(meeting : Meeting, date : PrimitiveDate) = maxSpeeds += (meeting -> date)
You haven't actually created a new type in your given example, it's simply an alias for the pre-existing Long type.
It's a technique I use quite often to deal with unwieldy nested connections. For example, I'd alias type Grid = Seq[Seq[Int]]
to avoid having to specify Seq[Seq[Int]]
over and over again for various parameters.
You can quite happily pass a Long
to a method taking a PrimitiveDate
method, although you do have the advantage that the code is much better self-documented.
If you really do want to create a new type with enforced type-safety and convenient pattern matching, I'd use a case class:
case class PrimitiveDate(value:Long)
and, potentially, even provide an implicit Long=>PrimitiveDate conversion for convenience.
11 months after you asked this question, Miles Sabin discovered a very simple, elegant and performant way of creating unboxed newtypes in Scala. Unlike type aliases, type tags are enforced. Primitive types need minimal boilerplate (one line per primitive) to provide specialization.
A year later, he added a more polished and robust version of this to Shapeless. The concept is simple and concise enough to be duplicated in a project without adding Shapeless if you don't want the rest of that excellent library.
Of course, both you and the people who answered your question probably know this, but it's worth adding here because this is still an important question.
精彩评论