I am trying to create a Vector class that is generic for all numeric types. my original attempt was to write a class for all Types like this:
class Vector3f(
You can't. Not right now. Maybe when, and if, Numeric
gets specialized.
Say you get the simplest parameterized class possible:
class Vector3[@specialized T](val x: T, val y: T, val z: T)(implicit num: Numeric[T]) {
def +(other: Vector3[T]) = new Vector3(num.plus(x, other.x), num.plus(y, other.y), num.plus(z, other.z))
}
The method +
will compile into something roughly like this:
override <specialized> def +$mcD$sp(other: Vector3): Vector3 = new Vector3$mcD$sp(
scala.Double.unbox(
Vector3$mcD$sp.this.Vector3$$num.plus(
scala.Double.box(Vector3$mcD$sp.this.x()),
scala.Double.box(other.x$mcD$sp()))),
scala.Double.unbox(
Vector3$mcD$sp.this.Vector3$$num.plus(
scala.Double.box(Vector3$mcD$sp.this.y()),
scala.Double.box(other.y$mcD$sp()))),
scala.Double.unbox(
Vector3$mcD$sp.this.Vector3$$num.plus(
scala.Double.box(Vector3$mcD$sp.this.z()),
scala.Double.box(other.z$mcD$sp()))),
Vector3$mcD$sp.this.Vector3$$num);
That's scalac -optimize -Xprint:jvm
output. Now there are even subclasses for each specialized type, so that you can initialize a Vector3
without boxing, but as long as Numeric
is not specialized, you can't go further.
Well... you can write your own Numeric
and specialize that, but, at that point, I'm not sure what you are gaining by making the class parameterized in first place.
The short answer is: you can't get full performance. Or at least I haven't found anything that gives full performance. (And I have tried for a while in exactly this use case; I gave up and wrote a code generator instead, especially since you can't handle different vector sizes generically either.)
I'd be delighted to be shown otherwise, but thus far everything I've tried has had a small (30%) to vast (900%) increase in runtime.
Edit: here's a test showing what I mean.
object Specs {
def ptime[T](f: => T): T = {
val t0 = System.nanoTime
val ans = f
printf("Elapsed: %.3f s\n",(System.nanoTime-t0)*1e-9)
ans
}
def lots[T](n: Int, f: => T): T = if (n>1) { f; lots(n-1,f) } else f
sealed abstract class SpecNum[@specialized(Int,Double) T] {
def plus(a: T, b: T): T
}
implicit object SpecInt extends SpecNum[Int] {
def plus(a: Int, b: Int) = a + b
}
final class Vek[@specialized(Int,Double) T](val x: T, val y: T) {
def +(v: Vek[T])(implicit ev: SpecNum[T]) = new Vek[T](ev.plus(x,v.x), ev.plus(y,v.y))
}
final class Uek[@specialized(Int,Double) T](var x: T, var y: T) {
def +=(u: Uek[T])(implicit ev: SpecNum[T]) = { x = ev.plus(x,u.x); y = ev.plus(y,u.y); this }
}
final class Veq(val x: Int, val y: Int) {
def +(v: Veq) = new Veq(x + v.x, y + v.y)
}
final class Ueq(var x: Int, var y: Int) {
def +=(u: Ueq) = { x += u.x; y += u.y; this }
}
def main(args: Array[String]) {
for (i <- 1 to 6) {
ptime(lots(1000000,{val v = new Vek[Int](3,5); var u = new Vek[Int](0,0); var i=0; while (i<100) { u = (u+v); i += 1 }; u}))
ptime(lots(1000000,{val v = new Veq(3,5); var u = new Veq(0,0); var i=0; while (i<100) { u = (u+v); i += 1 }; u}))
ptime(lots(1000000,{val v = new Uek[Int](3,5); val u = new Uek[Int](0,0); var i=0; while (i<100) { u += v; i += 1 }; u}))
ptime(lots(1000000,{val v = new Ueq(3,5); val u = new Ueq(0,0); var i=0; while (i<100) { u += v; i += 1 }; u}))
}
}
}
and the output:
Elapsed: 0.939 s
Elapsed: 0.535 s
Elapsed: 0.077 s
Elapsed: 0.075 s
Elapsed: 0.947 s
Elapsed: 0.352 s
Elapsed: 0.064 s
Elapsed: 0.063 s
Elapsed: 0.804 s
Elapsed: 0.360 s
Elapsed: 0.064 s
Elapsed: 0.062 s
Elapsed: 0.521 s <- Immutable specialized with custom numeric
Elapsed: 0.364 s <- Immutable primitive type
Elapsed: 0.065 s <- Mutable specialized with custom numeric
Elapsed: 0.065 s <- Mutable primitive type
...
You probably want to use the typeclass pattern as described here: http://dcsobral.blogspot.com/2010/06/implicit-tricks-type-class-pattern.html
Or, you can indirectly use by by using the Numeric trait http://www.scala-lang.org/api/current/scala/math/Numeric.html