如何减少Scala中创建的对象数量?

时间:2012-11-30 13:28:47

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

我在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如何知道我快速扔掉它?令人困惑。

  4. 所以一般来说,我的问题是如何让这个getPixelRGB更加内存友好?我还应该担心吗?

5 个答案:

答案 0 :(得分:13)

您可以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中享受无类内存布局。

答案 1 :(得分:5)

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

  

GC如何知道我快速抛弃[短命物体]?

现代地方选区的工作是基于观察到不同生命期的物体表现得非常不同。所以它在所谓的中管理它们。刚创建的对象存储在 eden 空间中。当它填满时,其中仍然被引用的所有对象(即它们都是活着的)被复制到所谓的年轻代空间。因此,所有死亡的物体都被留下,并且它们占据的空间几乎没有任何回收。这就是使短期对象对JVM如此便宜的原因。并且平均程序创建的大多数对象都是临时或局部变量,这些变量很快就会超出范围。

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

答案 2 :(得分: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)
)

答案 3 :(得分:3)

  

据说,它们是短暂的对象,我已经读过垃圾收集器应该快速重新声明它们。不过我还是很担心。 GC如何知道我快速扔掉它?太混乱了。

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

  • 几乎所有物品都很年轻
  • 几乎没有旧对象包含对新对象的引用

满足这一假设的对象非常便宜(事实上,比C语言中的mallocfree更便宜,只有违反一个或两个的对象)假设是昂贵的。

答案 4 :(得分:1)

您可以拥有一个返回简单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
}