如何使一个类适用于所有数值类型?

25
我正在尝试创建一个适用于所有数字类型的向量类(Vector class)。我的最初尝试是编写一个针对所有类型的类,像这样:

class Vector3f(val x:Float, val y:Float, val z:Float)

由于Scala支持"specialised"注解,因此我可以使用它来为所有数值类型生成这些类。

class Vector3[A <: What?](val x:A,val y:A, val z:A)

我发现所有数值的超类型都是AnyVal,但是AnyVal不支持+ - * /。那么正确的方法是什么,但是又不会牺牲未装箱数字类型的性能呢?


你可能想看一下这个问题:http://stackoverflow.com/questions/4436936/scala-compiler-not-recognizing-a-view-bound/4437336#4437336 - Madoc
3个回答

16
你现在无法做到。也许当且仅当“Numeric”得到专门化时,才有可能。
假设你得到了最简单的参数化类:
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))
}

方法+的编译结果大致如下:
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);

这是scalac -optimize -Xprint:jvm的输出。现在,每个特定类型都有子类,因此您可以初始化一个Vector3而不需要装箱,但只要Numeric未经专门化,您就无法进一步操作。
嗯...你可以编写自己的Numeric并将其专门化,但此时,我不确定首先将类参数化会带来什么好处。

8
短暂的回答是:您无法获得完整的性能。或者至少我没有找到任何可以提供完整性能的东西。(我已经在这个使用案例中尝试了一段时间;我放弃了并编写了一个代码生成器,特别是因为您也无法通用地处理不同的向量大小。)我很高兴被证明是错误的,但到目前为止,我尝试的所有方法都会导致运行时间小(30%)到大(900%)的增加。
编辑:这里是一个测试,展示了我的意思。
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}))
    }
  }
}

输出结果:

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
...

不支持2.8.1版本。在早期的2.8版本(晚期的2.8.0 RC,如果我没记错的话)存在一些问题,导致性能表现不太理想。我想我应该再试一次。 - Rex Kerr
刚刚再试了一次。在使用Sun JVM进行可变操作时似乎还可以,但一旦需要创建新对象,则会有约2倍的惩罚。(JRockit JVM显示相同趋势,但所有时间都比较糟糕,通常情况下会比2-3倍更差,并且只能有时候使可变专用案例起作用。) - Rex Kerr
1
我在Scala 2.9上对这段代码进行了基准测试,并获得了大致相同的结果。有趣的是,“-optimize”标志使第一种情况(不可变专用)变慢了约两倍。显然,应该避免使用-optimize!此外,“-server” JVM选项可以将前两种情况(不可变)提高约20%。 - Kipton Barros
还应该提到,@specialized注释确实有帮助。如果将它们移除,则第1个和第3个情况都会变慢,大约需要1.0秒。 - Kipton Barros

6

1
有点有趣的是,这个答案中链接的博客文章是由那个提供了更全面但得分较低的答案的人写的。 :) - Ry4an Brase
是的,我也注意到博客链接中的“dcsobral”,并且已经看到了丹尼尔在上面的回答。 - WestCoastProjects

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接