Spark - 如何在迭代(或递归)函数调用的情况下处理惰性求值

时间:2016-11-11 21:32:31

标签: scala apache-spark functional-programming lazy-evaluation

我有一个递归函数,需要将当前调用的结果与之前的调用进行比较,以确定它是否已达到收敛。我的函数不包含任何action - 它只包含mapflatMapreduceByKey。由于Spark不评估转换(直到调用一个动作),我的下一次迭代没有得到正确的值来比较收敛。

这是函数的骨架 -

def func1(sc: SparkContext, nodes:RDD[List[Long]], didConverge: Boolean, changeCount: Int) RDD[(Long] = {

   if (didConverge)
      nodes
   else { 
       val currChangeCount = sc.accumulator(0, "xyz")         
       val newNodes = performSomeOps(nodes, currChangeCount) // does a few map/flatMap/reduceByKey operations
       if (currChangeCount.value == changeCount)  {
          func1(sc, newNodes, true, currChangeCount.value)
       } else {
          func1(sc, newNode, false, currChangeCount.value)
       }
   }
}

performSomeOps仅包含mapflatMapreduceByKey转换。由于它没有任何操作,performSomeOps中的代码不会执行。所以我的currChangeCount没有得到实际数。这意味着,检查收敛的条件(currChangeCount.value == changeCount)将无效。要克服的一种方法是通过调用count在每次迭代中强制执行操作,但这是不必要的开销。

我想知道我可以做些什么来强制执行不需要太多开销的操作,还是有其他方法可以解决这个问题?

3 个答案:

答案 0 :(得分:2)

我相信这里有一个非常important thing

  

对于仅在操作内执行的累加器更新,Spark保证每个任务对累加器的更新仅应用一次,即重新启动的任务不会更新该值。在转换中,用户应该知道,如果重新执行任务或作业阶段,每个任务的更新可能会被多次应用。

由于累加器无法可靠地用于管理控制流程,因此更适合作业监控。

此外,执行操作不是不必要的开销。如果你想知道计算的结果是什么,你必须执行它。除非结果当然是微不足道的。最便宜的行动是:

rdd.foreach { case _ =>  }

但它不会解决你在这里遇到的问题。

一般来说,Spark中的迭代计算结构如下:

def func1(chcekpoinInterval: Int)(sc: SparkContext, nodes:RDD[List[Long]], 
    didConverge: Boolean, changeCount: Int, iteration: Int) RDD[(Long] = {

  if (didConverge) nodes
  else {

    // Compute and cache new nodes
    val newNodes = performSomeOps(nodes, currChangeCount).cache

    // Periodically checkpoint to avoid stack overflow
    if (iteration % checkpointInterval == 0) newNodes.checkpoint

    /* Call a function which computes values
     that determines control flow. This execute an action on newNodes.
    */
    val changeCount = computeChangeCount(newNodes)

    // Unpersist old nodes
    nodes.unpersist

    func1(checkpointInterval)(
      sc, newNodes, currChangeCount.value == changeCount, 
      currChangeCount.value, iteration + 1
    )
  }
}

答案 1 :(得分:0)

我看到这些map/flatMap/reduceByKey转换正在更新累加器。因此,执行所有更新的唯一方法是执行所有这些功能,与count + cache {{{1} {1}}或count)。

答案 2 :(得分:0)

以前的答案让我走上正确的轨道来解决类似的收敛检测问题。

foreachthe docs中显示为:

  

foreach(func):在数据集的每个元素上运行函数 func 。这通常用于副作用,例如更新累加器或与外部存储系统交互。

似乎而不是使用 rdd.foreach() 作为廉价操作来触发放置在各种转换中的累加器增量,它应该用于执行递增本身。

我无法生成一个scala示例,但这是一个基本的java版本,如果它仍然可以帮助:

// Convergence is reached when two iterations
// return the same number of results
long previousCount = -1;
long currentCount = 0;

while (previousCount != currentCount){
    rdd = doSomethingThatUpdatesRdd(rdd);

    // Count entries in new rdd with foreach + accumulator
    rdd.foreach(tuple -> accumulator.add(1));

    // Update helper values
    previousCount = currentCount;
    currentCount = accumulator.sum();
    accumulator.reset();
}
// Convergence is reached