使用lazy val 缓存字符串表示
我在 JAXMag 的 Scala 特刊中遇到了以下代码:
package com.weiglewilczek.gameoflife
case class Cell(x: Int, y: Int) {
override def toString = position
private lazy val position = "(%s, %s)".format(x, y)
}
Does the use of lazy val
in the 上述代码提供了比以下代码显着更高的性能?
package com.weiglewilczek.gameoflife
case class Cell(x: Int, y: Int) {
override def toString = "(%s, %s)".format(x, y)
}
或者这只是不必要的优化?
I encountered the following code in JAXMag's Scala special issue:
package com.weiglewilczek.gameoflife
case class Cell(x: Int, y: Int) {
override def toString = position
private lazy val position = "(%s, %s)".format(x, y)
}
Does the use of lazy val
in the above code provide considerably more performance than the following code?
package com.weiglewilczek.gameoflife
case class Cell(x: Int, y: Int) {
override def toString = "(%s, %s)".format(x, y)
}
Or is it just a case of unnecessary optimization?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
关于惰性值需要注意的一件事是,虽然它们只计算一次,但对它们的每次访问都受到双重检查锁定包装器的保护。这对于防止两个不同的线程尝试同时初始化该值并产生令人捧腹的结果是必要的。现在,双重检查锁定非常高效(现在它实际上在 JVM 中工作),并且在大多数情况下不需要获取锁,但比简单的值访问有更多的开销。
另外(有点明显),通过缓存对象的字符串表示形式,您可以显式地牺牲 CPU 周期,以换取可能大幅增加的内存使用量。 “def”版本中的字符串可以被垃圾收集,而“lazy val”版本中的字符串则不能。
最后,与性能问题的情况一样,如果没有基于事实的基准测试,基于理论的假设几乎毫无意义。如果不进行分析,您永远无法确定,所以不妨尝试一下看看。
One thing to note about lazy vals is that, while they are only calculated once, every access to them is protected by a double-checked locking wrapper. This is necessary to prevent two different threads from attempting to initialize the value at the same time with hilarious results. Now double-checked locking is pretty efficient (now that it actually works in the JVM), and won't require lock acquisition in most cases, but there is more overhead than a simple value access.
Additionally (and somewhat obviously), by caching the string representation of your object, you are explicitly trading off CPU cycles for possibly large increases in memory usage. The strings in the "def" version can be garbage-collected, while those in the "lazy val" version will not be.
Finally, as is always the case with performance questions, theory-based hypotheses mean nearly nothing without fact-based benchmarking. You'll never know for sure without profiling, so might as well try it and see.
toString
可以直接用lazy val
覆盖。请注意,
def
不能覆盖val
——您只能使子类中的成员更加稳定。toString
can be directly overriden with alazy val
.Note that a
def
may not override aval
-- you can only make members more stable in the sub class.在第一个片段中,
position
将根据需要仅计算一次,[when|if]toString
方法被调用。在第二个片段中,每次调用该方法时都会重新评估toString
body。鉴于x
和y
无法更改,这是没有意义的,并且应该存储toString
值。In the first snippet
position
will be calculated just once, on demand, [when|if]toString
method is called. In the second snippet,toString
body will be re-evaluated every time the method is called. Given thatx
andy
cannot be changed, it's senseless, andtoString
value should be stored.根据定义,案例类是不可变的。 toString 返回的任何值本身也是不可变的。因此,通过利用惰性 val 来“缓存”该值是有意义的。另一方面,提供的 toString 实现的作用与所有案例类提供的默认 toString 没什么区别。如果普通案例类 toString 在下面使用了惰性 val,我不会感到惊讶。
Case classes are, by definition, immutable. Any value returned by toString will itself be immutable, too. Thus it makes sense to essentially "cache" this value by utilizing a lazy val. On the other hand, the provided toString implementation does little more than the default toString provided by all case classes. I would not be surprised if a vanilla case class toString used a lazy val underneath.
对我来说看起来像是一个微观优化。 JVM 足以处理此类情况。
Looks like a micro-optimization to me. JVM is able enough to take care of such cases.