【问题标题】:Scala : Does variable type inference affect performance?Scala:变量类型推断会影响性能吗?
【发布时间】:2014-12-26 08:40:48
【问题描述】:

在 Scala 中,可以通过指定类型来声明变量,如下所示:(方法 1)

var x : String = "Hello World"

或者你可以让Scala自动检测变量类型(方法2)

var x = "Hello World"

为什么要使用方法 1?它有性能优势吗?
并且一旦声明了变量,无论是通过方法 1 还是方法 2 声明,它在所有情况下的行为都会完全相同吗?

【问题讨论】:

  • 如果它影响性能(我不这么认为),它几乎不会引起注意,它只会缩短您的代码,例如在您的情况下,很明显 x 是一个字符串。
  • 它不会,生成完全相同的字节码。它最多只能影响编译器速度。在运行时没有任何区别

标签: scala types type-inference


【解决方案1】:

类型推断是在编译时完成的 - 它本质上是编译器弄清楚你的意思,填补空白,然后编译生成的代码。

这意味着类型推断不会有运行时成本。然而,编译的时间成本有时会令人望而却步,需要您显式地注释某些表达式。

【讨论】:

  • 你确定吗?我不这么认为。我认为在确保类型已被注释时,编译器无论如何都需要进行自己的推理并将其与您的匹配,不是吗?
  • 好吧,让我这么说吧。如果您键入 annotate 方法,则编译器只有一种类型要检查。如果你不这样做,它必须检查所有可以匹配返回值的类型,并选择程序的其余部分仍然类型检查的最严格的类型。所以,不,不完全一样。
  • 我明白了。这说明了。我对我的编译器概念有点生疏,但非常感谢您的评论。
  • Hindley-Milner 类型系统运行时成本是指数级的。
【解决方案2】:

使用这两种变体不会有任何性能差异。 它们都将被编译为相同的代码。

【讨论】:

    【解决方案3】:

    其他答案假设编译器推断出您认为它推断的内容。

    很容易证明,在定义中指定类型将为定义的 RHS 设置预期类型并指导类型推断。

    例如,在这个构建某物集合的方法中,A 被推断为Nothing,这可能不是您想要的:

    scala> def build[A, B, C <: Iterable[B]](bs: B*)(implicit cbf: CanBuildFrom[A, B, C]): C = {
         | val b = cbf(); println(b.getClass); b ++= bs; b.result }
    build: [A, B, C <: Iterable[B]](bs: B*)(implicit cbf: scala.collection.generic.CanBuildFrom[A,B,C])C
    
    scala> val xs = build(1,2,3)
    class scala.collection.immutable.VectorBuilder
    xs: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3)
    
    scala> val xs: List[Int] = build(1,2,3)
    class scala.collection.mutable.ListBuffer
    xs: List[Int] = List(1, 2, 3)
    
    scala> val xs: Seq[Int] = build(1,2,3)
    class scala.collection.immutable.VectorBuilder
    xs: Seq[Int] = Vector(1, 2, 3)
    

    显然,获取 List 还是 Vector 对运行时性能很重要。

    这是一个蹩脚的例子,但在许多表达式中你不会注意到中间集合的类型,除非它导致性能问题。

    对话示例:

    https://groups.google.com/forum/#!msg/scala-language/mQ-bIXbC1zs/wgSD4Up5gYMJ

    http://grokbase.com/p/gg/scala-user/137mgpjg98/another-funny-quirk

    Why is Seq.newBuilder returning a ListBuffer?

    https://groups.google.com/forum/#!topic/scala-user/1SjYq_qFuKk

    【讨论】:

      【解决方案4】:

      在您给出的简单示例中,生成的字节码没有区别,因此性能没有区别。它也不会对编译速度产生明显影响。

      在更复杂的代码(可能涉及隐式)中,您可能会遇到通过指定某些类型会显着提高编译类型性能的情况。但是,我会完全忽略这一点,除非您遇到它——出于其他更好的原因指定类型或不指定类型。

      更符合您的问题,在一个非常重要的情况下,最好指定类型以确保良好的运行时性能。考虑这段代码:

      val x = new AnyRef { def sayHi() = println("Howdy!") }
      x.sayHi
      

      该代码使用反射来调用 sayHi,这对性能造成了巨大的影响。出于这个原因,最新版本的 Scala 会警告您此代码,除非您已为其启用语言功能:

      warning: reflective access of structural type member method sayHi should be enabled
      by making the implicit value scala.language.reflectiveCalls visible.
      This can be achieved by adding the import clause 'import scala.language.reflectiveCalls'
      or by setting the compiler option -language:reflectiveCalls.
      See the Scala docs for value scala.language.reflectiveCalls for a discussion
      why the feature should be explicitly enabled.
      

      然后您可以将代码更改为不使用反射的代码:

      trait Talkative extends AnyRef { def sayHi(): Unit }
      val x = new Talkative { def sayHi() = println("Howdy!") }
      x.sayHi
      

      因此,您通常希望在以这种方式定义类时指定变量的类型;这样,如果您无意中添加了一个需要反射才能调用的方法,您将得到一个编译错误——该方法不会为变量的类型定义。因此,虽然指定类型不会使代码运行得更快,但 如果代码会很慢,指定类型会导致编译失败。

      val x: AnyRef = new AnyRef { def sayHi() = println("Howdy!") }
      x.sayHi  // ERROR: sayHi is not defined on AnyRef
      

      当然还有其他原因可能会导致您想要指定类型。它们是方法/函数的形式参数以及递归或重载方法的返回类型所必需的。

      此外,您应该始终为公共 API 中的方法指定返回类型(除非它们非常明显),否则您最终可能会得到与您预期不同的方法签名,然后冒着破坏 API 现有客户端的风险修复签名。

      你当然可能想故意扩大一个类型,以便以后可以将其他类型的东西分配给一个变量,例如

      var shape: Shape = new Circle(1.0)
      shape = new Square(1.0)
      

      但在这些情况下,不会影响性能。

      指定类型也有可能导致转换,当然这会对转换产生的任何性能影响产生影响。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-25
        • 1970-01-01
        相关资源
        最近更新 更多