Performance scala隐式性能

Performance scala隐式性能,performance,scala,Performance,Scala,这个问题经常出现。使用泛型编码的函数在scala中的速度明显较慢。见下面的例子。类型特定版本的执行速度比通用版本快约1/3。考虑到通用组件不在昂贵的循环中,这一点令人倍感意外。对此有一个已知的解释吗 def xxxx_flttn[T](v: Array[Array[T]])(implicit m: Manifest[T]): Array[T] = { val I = v.length if (I <= 0) Array.ofDim[T](0) else {

这个问题经常出现。使用泛型编码的函数在scala中的速度明显较慢。见下面的例子。类型特定版本的执行速度比通用版本快约1/3。考虑到通用组件不在昂贵的循环中,这一点令人倍感意外。对此有一个已知的解释吗

  def xxxx_flttn[T](v: Array[Array[T]])(implicit m: Manifest[T]): Array[T] = {
    val I = v.length
    if (I <= 0) Array.ofDim[T](0)
    else {
      val J = v(0).length
      for (i <- 1 until I) if (v(i).length != J) throw new utl_err("2D matrix not symetric. cannot be flattened. first row has " + J + " elements. row " + i + " has " + v(i).length)
      val flt = Array.ofDim[T](I * J)
      for (i <- 0 until I; j <- 0 until J) flt(i * J + j) = v(i)(j)
      flt
    }
  }
  def flttn(v: Array[Array[Double]]): Array[Double] = {
    val I = v.length
    if (I <= 0) Array.ofDim[Double](0)
    else {
      val J = v(0).length
      for (i <- 1 until I) if (v(i).length != J) throw new utl_err("2D matrix not symetric. cannot be flattened. first row has " + J + " elements. row " + i + " has " + v(i).length)
      val flt = Array.ofDim[Double](I * J)
      for (i <- 0 until I; j <- 0 until J) flt(i * J + j) = v(i)(j)
      flt
    }
  }
def xxxx_flttn[T](v:Array[Array[T]])(隐式m:Manifest[T]):Array[T]={
val I=v.长度

如果(I这是由于装箱,当您将泛型应用于基元类型并使用包含数组(或在方法签名或作为成员显示的类型)时

例子 在以下特性中,编译后,
进程
方法将采用一个已擦除的
数组[Any]

trait Foo[A]{
  def process(as: Array[A]): Int
}
如果选择
A
作为值/基元类型,如
Double
,则必须将其装箱。当以非通用方式(例如使用
A=Double
)写入特征时,
过程将编译为采用
数组[Double]
,这是JVM上一种独特的数组类型。这更有效,因为为了在
数组[Any]
中存储
Double
,必须将
Double
包装(装箱)到一个对象中,该对象的引用存储在数组中。特殊的
数组[Double]
可以将
双精度
作为64位值直接存储在内存中

@专用的
-注释
如果您觉得有冒险精神,可以尝试使用
@specialized
关键字(这是一个相当多的错误,并且经常会使编译器崩溃)。这会使
scalac
为所有或选定的基元类型编译类的特殊版本。这只有在类型签名(
get(a:a))中类型参数显示为普通时才有意义
,但不是
get(as:Seq[A])
)或者作为
数组的类型参数。我想如果专用化毫无意义,你会收到警告。

你不能真正说出你在这里测量的是什么——反正不是很好——因为
for
循环不如纯
while
循环快,而且内部操作相当便宜。如果我们用while循环——关键的双重迭代是

 var i = 0
  while (i<I) {
    var j = 0
    while (j<J) {
      flt(i * J + j) = v(i)(j)
      j += 1
    }
    i += 1
  }
  flt
这只是一系列跳转和低级操作(daload和dastore是从数组中加载和存储双精度的关键操作)

160:    getstatic   #30; //Field scala/runtime/ScalaRunTime$.MODULE$:Lscala/runtime/ScalaRunTime$;
163:    aload   7
165:    iload   6
167:    iload   4
169:    imul
170:    iload   5
172:    iadd
173:    getstatic   #30; //Field scala/runtime/ScalaRunTime$.MODULE$:Lscala/runtime/ScalaRunTime$;
176:    aload_1
177:    iload   6
179:    aaload
180:    iload   5
182:    invokevirtual   #107; //Method scala/runtime/ScalaRunTime$.array_apply:(Ljava/lang/Object;I)Ljava/lang/Object;
185:    invokevirtual   #111; //Method scala/runtime/ScalaRunTime$.array_update:(Ljava/lang/Object;ILjava/lang/Object;)V
188:    iload   5
190:    iconst_1
191:    iadd
192:    istore  5
正如您所见,它必须调用方法来应用和更新数组

2:   aload_3 
3:   instanceof      #98; //class "[Ljava/lang/Object;"
6:   ifeq    18
9:   aload_3   
10:  checkcast       #98; //class "[Ljava/lang/Object;"
13:  iload_2
14:  aaload 
15:  goto    183
18:  aload_3
19:  instanceof      #100; //class "[I"
22:  ifeq    37
25:  aload_3   
26:  checkcast       #100; //class "[I"
29:  iload_2
30:  iaload 
31:  invokestatic    #106; //Method scala/runtime/BoxesRunTime.boxToInteger:
34:  goto    183
37:  aload_3
38:  instanceof      #108; //class "[D"
41:  ifeq    56
44:  aload_3   
45:  checkcast       #108; //class "[D"
48:  iload_2
49:  daload 
50:  invokestatic    #112; //Method scala/runtime/BoxesRunTime.boxToDouble:(
53:  goto    183
它基本上必须测试每种类型的数组,如果它是您要查找的类型,则将其装箱。Double非常接近前面(第三个,共10个),但它仍然是一个相当大的开销,即使JVM可以识别代码最终是装箱/取消装箱的,因此实际上不需要分配内存。(我不确定它能做到这一点,但即使它能做到,也无法解决问题。)


那么,该怎么办呢?你可以试试[@specialized T],它会将你的代码扩展十倍,就像你自己编写每个基本数组操作一样。但是,在2.9中,专门化是有缺陷的(在2.10中应该更少),所以它可能无法按你希望的方式工作。如果速度是关键——首先,写while循环而不是for循环(或者至少使用-optimize进行编译,这有助于以大约两倍的倍数进行循环!)这是由于装箱,当你将泛型应用到一个原始类型并使用数组时。如果你感觉顺畅,你可以尝试<代码> @专门的< /Cord>关键字(它很漂亮,经常会崩溃)。。这在Java上同样缓慢,尽管问题的原因在那里更加明显,因为您必须使用
Java.lang.Double
而不是
Double
。同意,这些数字并不太可靠。是的,我以前注意到while v for loop的差异,但miles sabin试图向我保证这并不重要.现在我将使用while循环和重载来处理重要的原语(Int、Long、Double、Float)。非常感谢!@kardenal.mendoza-虽然迈尔斯几乎对所有事情都是正确的,但不幸的是,如果你说的是关键的内部循环,他在这里是错的。在我的机器上,-优化的
xxxx\u flttn
正如你所写,它可以每秒展平18000 100x100个阵列;
xxxx\u flttn
带有while循环或
flttn
写下它可以做大约28000/s;
flttn
和while循环(或者两个循环,或者只是内部的
j
循环,而
i
仍然是for循环)管理50000/s。for循环过去比这糟糕得多(仍然没有
-优化
),但是
while
在真正重要的时候仍然击败了他们。谢谢雷克斯。是的,我重复了你的实验,事实上,在100x100时,我看到
while版本的速度增加了一倍。随着矩阵大小的增加,两者收敛,当我达到1000x1000时,
while和` for之间的差异似乎很小。这是jit jit吗取消for循环嵌套调用?@kardenal.mendoza-我认为其他考虑因素(例如缓存位置)更有可能比其他开销更重要。有一种简单的方法可以判断:在每种情况下,每秒可以执行多少个元素?在我的情况下,
1000x1000
矩阵的速度会大大降低。abs每个元素在时间上的绝对差异大致保持不变。@kardenal.mendoza-没关系;速度有所下降,但没有我想象的那么快。现在我和你一样困惑。我不确定Scala和JVM是如何管理这一点的。也许JVM正在对更长的循环进行循环分析,并简化更多的循环?
2:   aload_3 
3:   instanceof      #98; //class "[Ljava/lang/Object;"
6:   ifeq    18
9:   aload_3   
10:  checkcast       #98; //class "[Ljava/lang/Object;"
13:  iload_2
14:  aaload 
15:  goto    183
18:  aload_3
19:  instanceof      #100; //class "[I"
22:  ifeq    37
25:  aload_3   
26:  checkcast       #100; //class "[I"
29:  iload_2
30:  iaload 
31:  invokestatic    #106; //Method scala/runtime/BoxesRunTime.boxToInteger:
34:  goto    183
37:  aload_3
38:  instanceof      #108; //class "[D"
41:  ifeq    56
44:  aload_3   
45:  checkcast       #108; //class "[D"
48:  iload_2
49:  daload 
50:  invokestatic    #112; //Method scala/runtime/BoxesRunTime.boxToDouble:(
53:  goto    183