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