当前位置: 首页 > 知识库问答 >
问题:

scala专门化——使用对象而不是类会导致减速?

长孙哲
2023-03-14

我做了一些基准测试,结果我不知道如何解释。

我有两个类对泛型数组做同样(计算量很大)的事情,它们都使用专门化(@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预热和平均值)
结果如下:(左列是原始JavaArrays.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

后者是在对象中定义的,非常糟糕(大约慢4倍)。

我在运行基准测试时使用了scalacoptimize选项和不使用scalacoptimize选项,两者没有明显的区别(只有使用optimize时编译速度较慢)。

共有1个答案

萧韬
2023-03-14

这只是众多专业化错误中的一个——我不确定这个错误是否在错误追踪器上被报告过。如果从排序中抛出异常,您将看到它调用的是第二个排序的泛型版本,而不是专用版本:

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$$排序(...)而不是...排序$mFc$sp(...)?糟糕的编译器,糟糕!

作为一种解决方法,您可以将私有方法包装到最终的帮助对象中,例如。

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) { ... }
}

无论出于何种原因,编译器随后意识到它应该调用专用变量。我还没有测试是否每个被另一个调用的特殊方法都需要在它自己的对象中;我将通过异常抛出技巧将其留给您。

 类似资料:
  • 问题内容: 因此,我有主类在运行时调用。在Secondary类中,在顶部的代码是。 如何在不引起堆栈溢出错误的情况下使用次要类中的所有方法和变量,反之亦然? 注意:它们不在构造函数中 问题答案: 您的Main类正在创建一个Secondary实例,在创建一个Main实例…,这会导致堆栈溢出错误。 我认为您只是希望对象之间能够相互引用,所以不要在构造函数中创建另一个类的新实例。将引用声明为实例变量,并

  • 以下构建设置在使用GCC(4.6.3)的Linux上运行良好,但在使用GCC(4.7.2)的MinGW上则不然。 在 Linux 上,我们有: 这正是我所期待的。在Windows上,我们有 我怀疑这和弱符号有关。也就是说,Linux我们在foo. o有 而且在baro 而在Windows上,我们在foo.o中有这样的功能 而且在baro 因此,没有弱符号可以覆盖。 解决这个问题的最好方法是什么?在

  • 我有一个模板函数doSomething(T),它接受任何类型的参数…类型基类除外。 所以我把doSomething模板专门用于类型Base的参数,所以它做了一些不同的事情。 然而,当我将派生类传递给doSomething时,它会打印“所有类型!”,而我希望它打印“Base!”,因为派生类本质上也是一个基类。 如果我有: 然后doSomething(d)也会打印“所有类型!”而不是“base!”,因

  • 这个问题演示了如何使用C++20概念为函数模板选择重载。我试图做一些类似的事情:为类模板选择专门化。 编译器将此附加实现视为使用不同约束对模板的重新声明。我尝试了几种不同的方式来表达我的意图,但没有一种能满足我所尝试的编译器。事实上,我甚至无法用和传统的SFINAE实现这一点--诚然,我完全有可能不太理解SFINAE。 我发现的唯一方法需要对每种整型和浮点型进行不同的专门化。 [是的,我知道还有几

  • 我正在研究一种需要对大矩阵进行数学运算的算法。基本上,该算法包括以下步骤: 输入:大小为n的两个向量u和v > 对于两个矩阵中的每个条目,应用一个函数f。返回两个矩阵M_u,M_v 求M_的本征值和本征向量。对于i=0,返回e_i,ev_i,。。。,n-1 计算每个特征向量的外积。返回一个矩阵O_i=e_i*转置(e_i),i=0,。。。,n-1 用e_i=e_i delta_i调整每个特征值,其

  • 主要内容:实例,实例,Scala 继承,实例,实例,实例,Scala 单例对象,实例,实例类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。 我们可以使用 new 关键字来创建类的对象,实例如下: 实例 class Point (xc : Int, yc : Int ) {     var x : Int = xc     var y : Int = yc