我做了一些基准测试,但得到的结果我不知道如何解释。
情况简而言之:
我有两个类使用通用数组做同样的(计算量大)的事情,它们都使用专门化(@specialized
,后来的@spec
)。一个类的定义如下:
class A [@spec T] {
def a(d: Array[T], c: Whatever[T], ...) = ...
...
}
第二:(单例)
object B {
def a[@spec T](d: Array[T], c: Whatever[T], ...) = ...
...
}
在第二种情况下,我的性能受到了巨大的影响。为什么会发生这种情况? (注:目前我不太了解 Java 字节码,也不太了解 Scala 编译器的内部结构。)
更多详细信息:
完整代码在这里:https://github.com/magicgoose/trashbox/tree/master/sorting_tests/src/magicgoose/sorting
这是从 Java 中剥离出来的排序算法,(几乎)自动转换为 Scala,并将比较操作更改为通用操作,以允许使用与基本类型的自定义比较而无需装箱。加上简单的基准测试(不同长度的测试,JVM 预热和平均)
结果如下:(左列是原始 Java Arrays.sort(int[])
)
JavaSort | JavaSortGen$mcI$sp | JavaSortGenSingleton$mcI$sp
length 2 | time 0.00003ms | length 2 | time 0.00004ms | length 2 | time 0.00006ms
length 3 | time 0.00003ms | length 3 | time 0.00005ms | length 3 | time 0.00011ms
length 4 | time 0.00005ms | length 4 | time 0.00006ms | length 4 | time 0.00017ms
length 6 | time 0.00008ms | length 6 | time 0.00010ms | length 6 | time 0.00036ms
length 9 | time 0.00013ms | length 9 | time 0.00015ms | length 9 | time 0.00069ms
length 13 | time 0.00022ms | length 13 | time 0.00028ms | length 13 | time 0.00135ms
length 19 | time 0.00037ms | length 19 | time 0.00040ms | length 19 | time 0.00245ms
length 28 | time 0.00072ms | length 28 | time 0.00060ms | length 28 | time 0.00490ms
length 42 | time 0.00127ms | length 42 | time 0.00096ms | length 42 | time 0.01018ms
length 63 | time 0.00173ms | length 63 | time 0.00179ms | length 63 | time 0.01052ms
length 94 | time 0.00280ms | length 94 | time 0.00280ms | length 94 | time 0.01522ms
length 141 | time 0.00458ms | length 141 | time 0.00479ms | length 141 | time 0.02376ms
length 211 | time 0.00731ms | length 211 | time 0.00763ms | length 211 | time 0.03648ms
length 316 | time 0.01310ms | length 316 | time 0.01436ms | length 316 | time 0.06333ms
length 474 | time 0.02116ms | length 474 | time 0.02158ms | length 474 | time 0.09121ms
length 711 | time 0.03250ms | length 711 | time 0.03387ms | length 711 | time 0.14341ms
length 1066 | time 0.05099ms | length 1066 | time 0.05305ms | length 1066 | time 0.21971ms
length 1599 | time 0.08040ms | length 1599 | time 0.08349ms | length 1599 | time 0.33692ms
length 2398 | time 0.12971ms | length 2398 | time 0.13084ms | length 2398 | time 0.51396ms
length 3597 | time 0.20300ms | length 3597 | time 0.20893ms | length 3597 | time 0.79176ms
length 5395 | time 0.32087ms | length 5395 | time 0.32491ms | length 5395 | time 1.30021ms
后者是在 object
中定义的,它很糟糕(大约慢 4 倍)。
更新 1
我在使用和不使用 scalac optimise
选项的情况下运行了基准测试,没有明显的差异(只是使用 optimise
时编译速度较慢)。
最佳答案
这只是特化中的众多错误之一 - 我不确定错误跟踪器是否已报告此错误。如果您从排序中抛出异常,您将看到它调用第二个排序的通用版本而不是专用版本:
java.lang.Exception: Boom!
at magicgoose.sorting.DualPivotQuicksortGenSingleton$.magicgoose$sorting$DualPivotQuicksortGenSingleton$$sort(DualPivotQuicksortGenSingleton.scala:33)
at magicgoose.sorting.DualPivotQuicksortGenSingleton$.sort$mFc$sp(DualPivotQuicksortGenSingleton.scala:13)
请注意堆栈顶部的内容是 DualPivotQuicksortGenSingleton$$sort(...)
而不是 ...sort$mFc$sp(...)
?糟糕的编译器,糟糕!
作为解决方法,您可以将私有(private)方法包装在最终的辅助对象中,例如
def sort[@ spec T](a: Array[T]) { Helper.sort(a,0,a.length) }
private final object Helper {
def sort[@spec T](a: Array[T], i0: Int, i1: Int) { ... }
}
无论出于何种原因,编译器都会意识到它应该调用专门的变体。我还没有测试是否每个被另一个调用的专用方法都需要位于其自己的对象内;我将通过异常抛出技巧将其留给您。
关于scala 特化 - 使用对象而不是类会导致速度减慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16669276/