【问题标题】:Immutability and performance不变性和性能
【发布时间】:2019-01-07 20:26:18
【问题描述】:

我尝试搜索 stackoverflow 并且有许多相关主题,包括

val-mutable versus var-immutable in Scala

What is the difference between a var and val definition in Scala?

但我还是想澄清一下我的理解是否正确。

看起来规则正在遵循

更喜欢不可变的 val 而不是不可变的 var 而不是可变的 val 而不是可变的 变种。尤其是不可变 var 优于可变 val!

但不可变 var 的性能比可变 val 差得多,根据 REPL 中的简单测试

scala -J-Xmx2g

scala> import scala.collection.mutable.{Map => MMap}
import scala.collection.mutable.{Map=>MMap}

scala>

scala> def time[R](block: => R): R = {
     |   val t0 = System.nanoTime()
     |   val result = block    // call-by-name
     |   val t1 = System.nanoTime()
     |   println("Elapsed time: " + (t1 - t0) + "ns")
     |   result
     | }
time: [R](block: => R)R

scala>time {val mut_val = MMap[Int, Int](); for (i <- 1 to 1000000) mut_val += (i -> i)}
Elapsed time: 551073900ns

scala>time {var mut_var = MMap[Int, Int](); for (i <- 1 to 1000000) mut_var += (i -> i)}
Elapsed time: 574174400ns

scala>time {var imut_var = Map[Int, Int](); for (i <- 1 to 1000000) imut_var += (i -> i)}
Elapsed time: 860938800ns

scala>time {val mut_val = MMap[Int, Int](); for (i <- 1 to 2000000) mut_val += (i -> i)}
Elapsed time: 1103283000ns

scala>time {var mut_var = MMap[Int, Int](); for (i <- 1 to 2000000) mut_var += (i -> i)}
Elapsed time: 1166532600ns

scala>time {var imut_var = Map[Int, Int](); for (i <- 1 to 2000000) imut_var += (i -> i)}
Elapsed time: 2926954500ns

我还添加了 mutable var,即使很难,它也没有多大意义。 它的性能与 mutable val 非常相似,只是为了完成图片。 但是不可变 var 的性能要差得多。

因此,不可变 var 与可变 val 的代价是性能下降(以及更广泛的内存使用!)。有人可以再次解释支付这个价格的意义是什么吗?具体例子表示赞赏。

谢谢

【问题讨论】:

  • 您永远不会将单个元素一个接一个地添加到这样的不可变映射中。你宁愿做(1 to 2000000).view.map(i =&gt; i -&gt; i).toMap之类的事情,应该会更快。
  • 不可变性的优点在整个互联网上都有描述。您具体询问的是您所做的研究?
  • 好的,您测试了使用可变地图迭代构建 2M 元素地图会更快。这是预期的。但是你不能概括这个结果,即在任何地方使用可变结构更快更简单。
  • @Carcigenicate,我虽然问题很清楚。例如, mut val 和 imut var 都不是“参照透明的”。没有一个是线程安全的。等等。预期的答案是 imut var 可能更好的具体原因 - 就像在 kag0 的答案中解释的那样。
  • @DrYWit 你的问题很清楚,但是问不变性的好处是什么是一个过于模糊的问题,正如我所提到的,已经通过一些研究回答了这个问题。如果您需要根据先前的研究对某个特定点进行澄清,这将是一个很好的问题。

标签: scala performance immutability


【解决方案1】:

有很多方法可以回答这个问题(许多 cmets 驳斥了可变 val 总是表现更差的说法),但一句话概括:范围和副作用。

不可变 var 的范围仅限于声明它的位置,并且将其作为参数传递或将其分配给另一个变量通常会按预期运行。
另一方面,可变 val 会感受到状态的变化,无论它在哪里使用和传递。

考虑一个应用程序,您希望在其中运行多个具有不同配置的工作程序,这些工作程序继承自默认值。 (假设TIMEOUT_1 = t1PARALLEL_FACTOR_1 = pf1TIMEOUT_2 = t2 等)

使用不可变变量

var defaultConfig = immutable.Map("timeout" → "5", "parallelFactor" → "4")
var worker1Config = defaultConfig
var worker2Config = defaultConfig

worker1Config += "timeout" → sys.env("TIMEOUT_1")
worker1Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_1")

worker2Config += "timeout" → sys.env("TIMEOUT_2")
worker2Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_2")

println(defaultConfig) // Map(timeout -> 5, parallelFactor -> 4)
println(worker1Config) // Map(timeout -> t1, parallelFactor -> pf1)
println(worker2Config) // Map(timeout -> t2, parallelFactor -> pf2)

可变变量

val defaultConfig = mutable.Map("timeout" → "5", "parallelFactor" → "4")
val worker1Config = defaultConfig
val worker2Config = defaultConfig

worker1Config += "timeout" → sys.env("TIMEOUT_1")
worker1Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_1")

worker2Config += "timeout" → sys.env("TIMEOUT_2")
worker2Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_2")

println(defaultConfig) // Map(parallelFactor -> pf2, timeout -> t2)
println(worker1Config) // Map(parallelFactor -> pf2, timeout -> t2)
println(worker2Config) // Map(parallelFactor -> pf2, timeout -> t2)

您可以看到,即使几乎所有代码都相同,但使用可变 val 会引入一个不明显的错误(尤其是如果这些代码部分位于不同的函数中而不是全部在一起时)。

【讨论】:

  • 很好的例子,谢谢。很明显为什么 defaultConfig 但有点出乎意料的是可变映射中的“顺序”没有被保留。我只测试了一个工作配置和 3 个(键 -> 值)对 worker1Config += "timeout" -> "t1"; worker1Config += "parallelFactor" -> "pf1"; worker1Config += "dummy" -> "dummy" 并且结果低于 scala.collection.mutable.Map[String,String] = Map(parallelFactor -> pf1, dummy -> dummy, timeout -> t1)
猜你喜欢
  • 1970-01-01
  • 2010-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-02
  • 1970-01-01
  • 1970-01-01
  • 2012-02-29
相关资源
最近更新 更多