【问题标题】:How to reduce the number of objects created in Scala?如何减少在 Scala 中创建的对象数量?
【发布时间】:2012-11-18 17:37:27
【问题描述】:

我正在用 Scala 编写一个计算机图形应用程序,它使用 RGB 类返回图像中某个点的颜色。可以想象,返回颜色 RGB 对象的函数被调用了很多次。

class RGB(val red: Int, val green: Int, val blue: Int) { }

有一个函数getPixelRGB,常用如下

val color:RGB = getPixelRGB(image, x, y)

问题是我可能会调用这个函数一百万次,然后我相信会生成一百万个唯一的 RGB 对象实例,这是一个非常没有吸引力的情况。我对此有一些想法:

  1. 如果 getPixelRGB 被调用无数次,它可能会创建无限数量的对象,但它不一定是无限数量的对象,因为最多只有 255 * 255 * 255 种可能的组合可以生产RGB。所以创建的对象数量“应该”是有限的。可以调整此函数以使用对象池,如果它要返回与某个时间相同的颜色,则它可以返回该颜色的相同池对象实例。

  2. 我可以将此 RGB 编码为 Int。 Int 的内存开销比普通的 Scala/Java 对象少,Java 对象有额外的内存开销。由于 Scala Int 类型是 4 个字节宽,前 3 个字节可以存储 RGB 值。我假设只从 getPixelRGB 方法返回 Int 而不是 RGB 会减少内存开销。但是,如何在仍然具有 RGB 类的说服力的同时做到这一点?

  3. 据推测,它们是短命的对象,我已经读过垃圾收集器应该迅速重新声明它们。不过我还是很担心。 GC 怎么知道我很快就把它扔掉了?太混乱了。

所以总的来说,我的问题是如何让这个 getPixelRGB 对内存更友好?我也应该担心吗?

【问题讨论】:

  • 在性能关键型应用程序中,将值编码为整数是一种标准方法。使用对象不是一种选择,因为来回转换值。在这里,Bitshifts 是你的朋友(也许是噩梦)。

标签: scala memory memory-management garbage-collection jvm


【解决方案1】:

您可以encode RGB with single longint。此外,在 scala 2.10 中,您可以为原始值定义 value class,例如

class RGB private(val underlying: Long) extends AnyVal {
  def toTriple = /*decoding to (red, green, blue)*/
} 
object RGB {
  def apply(red: Int, green: Int, blue: Int) = /* encode and create class with new RGB(longvalue)*/
}

使用值类,您仍然可以拥有类型信息并在 JVM 中享受无类内存布局。

【讨论】:

  • 我会奖励这个,因为价值等级确实解决了这里的主要问题。然而,其他人也有一些好的想法。
【解决方案2】:

您的问题 #3 尚未解决,所以我会试一试。

GC 怎么知道我正在快速扔掉 [short living objects]?

现代 GC 的工作是基于对不同生命周期的对象行为非常不同的观察。所以它以所谓的来管理它们。刚刚创建的对象存储在 eden 空间中。当它填满时,其中所有仍然被引用(即它们是活着的)的对象都会被复制到所谓的 young generation 空间。因此,所有死物都被留下,它们所占据的空间几乎为零。这就是使短期对象对 JVM 来说如此便宜的原因。并且大多数由普通程序创建的对象都是临时变量或局部变量,它们很快就会超出范围。

在第一轮 GC 之后,年轻代空间以类似的方式进行管理,只是它们可能更多。 GC 可以配置为让对象在年轻代空间中花费一轮或多轮。然后最终,最后的幸存者被迁移到幸存者(又名老一代)空间,他们将在那里度过余生。这个空间是通过定期应用经典标记和扫描技术的一些变体来管理的:遍历所有活动引用的图表并标记活动对象,然后通过压缩幸存者清除所有未标记(死)的对象到一个连续的内存块中,从而对空闲内存进行碎片整理。这是一项代价高昂的操作,会阻塞程序的执行,而且很难正确实现,尤其是在现代多线程 VM 中。这就是发明分代 GC 的原因,以确保创建的所有对象中只有一小部分能够到达这个阶段。

【讨论】:

  • 是的,知道它很有用,关于 GC 让我很困扰的一件事是缺乏控制,我来自 C/C++ 过去,对何时释放对象(最初)有严格的控制。在很多情况下,我创建的对象只是想拥有很短的时间,希望我能告诉 GC 更多关于我想如何保留这些对象的信息。
  • @Phil,我理解,因为我有类似的过去。随着时间的推移,我习惯了 GC 并学会理解它减轻了我肩上的沉重负担,使我能够专注于高级逻辑而不是迷失在低级细节中。但是,在某些情况下,确实需要 C++ 提供的严格控制。
【解决方案3】:

据说,它们是短命的对象,我已经读过垃圾收集器应该迅速重新声明它们。不过我还是很担心。 GC 怎么知道我很快就把它扔掉了?好混乱。

它不知道。它假设它。这被称为世代假设,所有世代垃圾收集器都建立在该假设之上:

  • 几乎所有对象都会在年轻时死去
  • 几乎没有旧对象包含对新对象的引用

满足这一假设的对象非常便宜(实际上,甚至比 C 等语言中的 mallocfree 还要便宜),只有违反一个或两个假设的对象才是昂贵的。

【讨论】:

    【解决方案4】:

    就内存友好性而言,最有效的解决方案是将完整的颜色信息存储在一个 Int.正如您正确提到的,颜色信息只需要三个字节,所以四个字节的 Int 就足够了。您可以使用位操作对来自一个 Int 的 RGB 信息进行编码和解码:

    def toColorCode(r: Int, g: Int, b: Int) = r << 16 | g << 8 | b
    
    def toRGB(code: Int): (Int, Int, Int) = (
      (code & 0xFF0000) >> 16, 
      (code & 0x00FF00) >> 8, 
      (code & 0x0000FF)
    )
    

    【讨论】:

      【解决方案5】:

      您可以有一个返回简单Int 的接口。然后,您可以在需要时使用隐式转换将Int 视为RGB 对象。

      case class RBGInt(red: Int, green: Int, blue: Int) {
         // ...
      }
      
      object Conversions { 
      
        implicit def toRGBInt(p: Int) = {
          val (r, g, b) = /* some bitmanipulation to turn p into 3 ints */
          RGBInt(r, g, b)
        }
      
      }
      

      那么您可以将任何Int 视为您认为有意义的RGBInt

      type RGB = Int // useful in documenting interfaces that consume
                     // or returns Ints which represent RGBs
      
      def getPixelRGB(img: Image, x: Int, y: Int): RGB = {
        // returns an Int
      }
      
      def someMethod(..) = {
        import Conversions._
        val px: RGB = getPixelRGB(...) // px is actually an Int
        px.red // px, an Int is lifted to an RGBInt
      }
      

      【讨论】:

      • 这如何解决 OP 的问题?我没有看到对象创建的任何减少。
      • 嗯,它至少应该确保只在需要时才创建对象,并且即使它们会持续很短的时间。最终会被传递(并在内存中停留更长时间)将是普通的 Ints 或 Ints 的集合。 JVM 对象创建速度非常快。
      • 我个人更喜欢@omnomnom 的建议 - 值类在 2.10 中会很好。
      猜你喜欢
      • 1970-01-01
      • 2018-09-14
      • 2018-01-03
      • 1970-01-01
      • 2016-01-23
      • 1970-01-01
      • 1970-01-01
      • 2021-01-22
      • 1970-01-01
      相关资源
      最近更新 更多