代码之家  ›  专栏  ›  技术社区  ›  Imran

是否应在Seq的已处理元素上进行GC?

  •  1
  • Imran  · 技术社区  · 6 年前

    以下是我的代码的简化版本:

    // Very small wrapper class for Large BigData object
    class LazilyEvaluatedBigData(a: String) {
        lazy val generate: BigData
    }
    
    // Contents of BigData are considered to be large
    class BigData {
        def process: Seq[Int]  // Short Seq in general, say 2-3 elements
    }
    
    val seq1: Seq[LazilyEvaluatedBigData]
    val seq2: Seq[LazilyEvaluatedBigData]
    
    val results1 = seq1.flatMap(_.generate.process)
    val results2 = seq2.flatMap(_.generate.process)
    

    现在,我希望在这里发生的是,在任何给定的时间,内存中只需要一个BigData类的实例。考虑到不需要将seq1或seq2的“已处理”元素保存在内存中,我希望它们被垃圾收集,但我的进程一直位于平面图的中间:(

    我是否对scala垃圾回收器期望过高。是否认为需要引用seq1和seq2的头部?


    最终修复是合并此类:

    class OnDemandLazilyEvaluatedBigData(a: String) {
      def generate(): LazilyEvaluatedBigData = new LazilyEvaluatedBigData(a)
    }
    

    然后将seq1和seq2转换为:

    val seq1: Seq[OnDemandLazilyEvaluatedBigData]
    
    1 回复  |  直到 6 年前
        1
  •  4
  •   Andrey Tyukin    6 年前

    您并没有对GC有太多的期望,但您假设了一些代码无法表达的内容。

    你有一个

    lazy val generate: BigData
    

    在您的 LazilyEvaluatedBigData 课堂上,你有

    val seq1: Seq[LazilyEvaluatedBigData]
    

    在正在执行的代码中。

    您的代码的行为符合预期,因为:

    • A. lazy val 不是 def :调用后,它保证将存储计算结果。如果您的程序内存不足,您不应该期望它会让其值被垃圾收集,并在需要时重新计算它。
    • A. Seq 保证不会丢失任何元素。例如,a List 不会因为程序内存不足而删除其任何元素。要做到这一点,您需要类似于带有软引用的序列的东西,或者您必须重写代码,以便在不再需要时不引用包含已处理元素的列表头。

    如果将这两点结合起来考虑,那么代码在 flatMap 这个 seq1 是保存对多个 LazlyEvaluatedBigData -实例,以及 懒惰的val 在那些里面 LazlyEvaluatedBigData -实例都被计算并保存在内存中。


    如果你想 BigData 当在 平面图 ,只需声明 generate

    def generate: BigData
    

    然后你的 序号1 seq2 只能装下 String s、 以及 平面图 将加载单个 BigData公司 比如,再把它挤压成一小块 Seq[Int] 使用 process ,然后 BigData公司 实例可能再次被垃圾收集。这将在没有太多内存的情况下成功运行:

    // Very small wrapper class for Large BigData object
    class LazilyEvaluatedBigData(a: String) {
        def generate: BigData = new BigData(128)
    }
    
    // Contents of BigData are large
    class BigData(m: Int) {
      val data = Array.ofDim[Byte](1000000 * m)
      def process: Seq[Int] = List(1,2,3)
    }
    
    val seq1: Seq[LazilyEvaluatedBigData] = List.fill(100)(new LazilyEvaluatedBigData(""))
    
    val results1 = seq1.flatMap(_.generate.process)
    
    println("run to end without OOM")
    

    (如果 懒惰的val )。

    另一种选择是使用软参考(草图,未经彻底测试):

    class LazilyEvaluatedBigData(a: String) {
      import scala.ref.SoftReference
      private def uncachedGenerate: BigData = new BigData(128)
    
      private var cachedBigData: Option[SoftReference[BigData]] = None
      def generate: BigData = {
        val resOpt = for {
          softRef <- cachedBigData
          bd <- softRef.get
        } yield bd
        if (resOpt.isEmpty) {
          val res = uncachedGenerate
          cachedBigData = Some(new SoftReference(res))
          res
        } else {
          resOpt.get
        }
      }
    }
    
    class BigData(m: Int) {
      val data = Array.ofDim[Byte](1000000 * m)
      def process: Seq[Int] = List(1,2,3)
    }
    
    val seq1: Seq[LazilyEvaluatedBigData] = List.fill(100)(new LazilyEvaluatedBigData(""))
    
    val results1 = seq1.flatMap(_.generate.process)
    
    println("run to end without OOM")
    

    这也不会抛出OOM错误,希望它更接近 LazlyEvaluatedBigData

    似乎无法替换 平面图 通过某种递归方法,确保 seq 因为 Seq公司 可以是任何东西,例如a Vector ,如果不重建结构的其余部分,就很难分离头部。有人可能会尝试构建 平面图 如果更换了 Seq公司 通过 列表 ,其中 head 可以更容易地进行gc。


    编辑

    如果而不是 Seq公司 你可以得到 列表 (这样可以对头部进行gc’d),那么这也可以:

    class LazilyEvaluatedBigData(a: String) {
      lazy val generate: BigData = new BigData(128)
    }
    
    class BigData(m: Int) {
      val data = Array.ofDim[Byte](1000000 * m)
      def process: Seq[Int] = List(1,2,3)
    }
    
    @annotation.tailrec
    def gcFriendlyFlatMap[A](xs: List[LazilyEvaluatedBigData], revAcc: List[A], f: BigData => List[A]): List[A] = {
      xs match {
        case h :: t => gcFriendlyFlatMap(t, f(h.generate).reverse ::: revAcc, f)
        case Nil => revAcc.reverse
      }
    }
    
    val results1 = gcFriendlyFlatMap(List.fill(100)(new LazilyEvaluatedBigData("")), Nil, _.process.toList)
    
    println("run to end without OOM")
    println("results1 = " + results1)
    

    然而,这似乎非常脆弱。上述示例之所以有效,是因为 gcFriendlyFlatMap 是尾部递归的。即使您添加了 看似无害的包装,比如

    def nicerInterfaceFlatMap[A](xs: List[LazilyEvaluatedBigData])(f: BigData => List[A]): List[A] = {
      gcFriendlyFlatMap(xs, Nil, f)
    }
    

    ,一切都随着一声“OOM”而中断。我认为(还有 @tailrec 这是因为 xs -列表保存在的堆栈框架上 nicerInterfaceFlatMap ,这样头部就不会被垃圾收集。

    如果你不能改变 懒惰的val 在里面 LazlyEvaluatedBigData ,我建议您围绕它构建一个包装器,在那里您可以控制引用。