通过Scala中的含义“装箱”原始类型的开销

时间:2010-10-20 09:27:44

标签: scala implicit-conversion

假设我想要一个类似Java Date的类。它唯一的数据成员是一个long,表示自1970年以来的毫秒数。

只是制作新的Scala类型会有什么性能优势:

type PrimitiveDate = Long

然后,您可以使用隐式转换添加方法,就像使用RichInt对int进行的那样。原始类型的这种“拳击”是否涉及任何开销(类创建)?基本上你可以有一个静态方法

def addMonth(date: PrimitiveDate, months: Int): PrimitiveDate = date + 2592000000 * months

让类型系统弄清楚它必须在d addMonth 5时应用  出现在您的代码中。

修改

scala编译器似乎没有强制通过编写type PrimitiveDate = Long创建的别名。创建一个合适的类,包含Long,这是在Scala中创建强制类型的唯一方法吗?

您认为能够为基本类型创建强制类型别名有多大用处?

3 个答案:

答案 0 :(得分:11)

嗯, escape analysis 应该意味着最新的JVM实际上不必创建丰富的包装来调用addMonth方法。

实际上在实践中发生的程度显然取决于JVM决定这些方法在对象创建中添加多少运行时热点。当没有进行转义分析时,显然JVM必须在包装类的新实例中“装箱”(如你所说)Long。它不涉及“类创建” - 它将涉及“创建类的实例”。这个短暂的实例将立即成为GC-d,因此开销(虽然很小)是:

  • 实例的内存分配
  • GC实例

如果您正在编写非常低延迟的代码,而您正在尝试最小化垃圾创建(在紧密循环中),这些显然只会出现任何问题。只有你知道是否是这种情况。

至于这种方法是否对您有效(并且逃避分析对您有帮助),您必须在野外进行测试。众所周知,微观基准很难为这类事情写作。


我不太喜欢这些类型别名是公共API 的一部分的原因是scala并没有像我希望的那样严格执行它们。例如:

type PrimitiveDate = Long
type PrimitiveSpeed = Long
type Car = String
type Meeting = String

var maxSpeeds : Map[Car, PrimitiveSpeed] = Map.empty

//OOPS - much too easy to accidentally send the wrong type
def setDate(meeting : Meeting, date : PrimitiveDate) = maxSpeeds += (meeting -> date)

答案 1 :(得分:4)

您实际上并未在给定示例中创建新类型,它只是预先存在的Long类型的别名。

这是我经常使用的一种技术来处理笨重的嵌套连接。例如,我使用别名type Grid = Seq[Seq[Int]]来避免必须一遍又一遍地为各种参数指定Seq[Seq[Int]]

您可以非常愉快地将Long传递给采用PrimitiveDate方法的方法,尽管执行的优势在于代码可以更好地自我记录。< / p>

如果你真的想创建一个强制类型安全和方便模式匹配的新类型,我会使用一个案例类:

case class PrimitiveDate(value:Long)

并且,为方便起见,甚至可能提供隐式的Long =&gt; PrimitiveDate转换。

答案 2 :(得分:3)

在你提出这个问题11个月后,Miles Sabin发现了一种在Scala中创建unboxed newtypes的非常简单,优雅和高效的方式。与类型别名不同,强制使用类型标记。原始类型需要最少的样板(每个基元一行)以提供专业化。

一年后,他向more polished and robust version添加了Shapeless。这个概念简单而简洁,可以在不添加Shapeless的情况下复制到项目中,如果你不想要那个优秀的库的其余部分。

当然,你和回答你问题的人都知道这一点,但值得在这里补充,因为这仍然是一个重要的问题。