【问题标题】:Parallel recursion in scalascala中的并行递归
【发布时间】:2013-05-26 22:45:53
【问题描述】:

我正在尝试并行化来自 25 lines Sudoku solver in Scala 的数独求解器的递归调用。我把他们的Fold 改成了reduce

def reduce(f: (Int, Int) => Int, accu: Int, l: Int, u: Int): Int = {
  accu + (l until u).toArray.reduce(f(accu, _) + f(accu, _))
}

如果按顺序运行可以正常工作,但是当我将其更改为

  accu + (l until u).toArray.par.reduce(f(accu, _) + f(accu, _))

递归更频繁地到达底部并产生错误的解决方案。我认为,它将执行底层递归并向上工作,但似乎没有这样做。
我也试过期货

def parForFut2(f: (Int, Int) => Int, accu: Int, l: Int, u: Int): Int = {
  var sum: Int = accu
  val vals = l until u
  vals.foreach(t => scala.actors.Futures.future(sum + f(accu, t)))
  sum
}

这似乎与par.reduce 有相同的问题。我会很感激任何评论。整个代码在这里:

object SudokuSolver extends App {
// The board is represented by an array of string 
val source = scala.io.Source.fromFile("./puzzle")
val lines = (source.getLines).toArray
var m: Array[Array[Char]] = for (
  str <- lines;
  line: Array[Char] = str.toArray
) yield line
source.close()

// For printing m
def print = {
  Console.println("");
  refArrayOps(m) map (carr => Console.println(new String(carr)))
}

// The test for validity of n on position x,y
def invalid(i: Int, x: Int, y: Int, n: Char): Boolean =
  i < 9 && (m(y)(i) == n || m(i)(x) == n ||
  m(y / 3 * 3 + i / 3)(x / 3 * 3 + i % 3) == n || invalid(i + 1, x, y, n))

// Looping over a half-closed range of consecutive Integers [l..u) 
// is factored out Into a higher-order function
def parReduce(f: (Int, Int) => Int, accu: Int, l: Int, u: Int): Int = {
  accu + (l until u).toArray.par.reduce(f(accu, _) + f(accu, _))
}

// The search function examines each position on the board in turn, 
// trying the numbers 1..9 in each unfilled position 
// The function is itself a higher-order fold, accumulating the value 
// accu by applying the given function f to it whenever a solution m 
// is found 
def search(x: Int, y: Int, f: (Int) => Int, accu: Int): Int = Pair(x, y) match {
  case Pair(9, y) => search(0, y + 1, f, accu) // next row 
  case Pair(0, 9) => f(accu) // found a solution - print it and continue
  case Pair(x, y) => if (m(y)(x) != '0') search(x + 1, y, f, accu) else
    parForFut1((accu: Int, n: Int) =>
      if (invalid(0, x, y, (n + 48).asInstanceOf[Char])) accu else {
        m(y)(x) = (n + 48).asInstanceOf[Char];
        val newaccu = search(x + 1, y, f, accu);
        m(y)(x) = '0';
        newaccu
      }, accu, 1, 10)
}

// The main part of the program uses the search function to accumulate 
// the total number of solutions 
Console.println("\n" + search(0, 0, i => { print; i + 1 }, 0) + " solution(s)")
} 

【问题讨论】:

  • 不要使用可变数据结构/变量;首先让你的方法成为纯函数,然后并行执行
  • 谢谢,最后确实有帮助。它让您了解当您不想更改时要更改哪些变量(值)。

标签: scala recursion concurrency


【解决方案1】:

据我所知,递归本质上是顺序的(即不可并行化)。

我会这样推理:递归意味着(简单地说)“打电话给自己”。函数调用总是发生在一个且恰好是一个线程内。

如果您告诉函数调用自己,那么您将留在该线程中 - 您没有划分工作(即使其并行)。

虽然递归和并行性是相关的,但不是在函数调用级别。它们是相关的,任务可以递归分解成更小的部分,可以并行执行。

【讨论】:

  • 按照这种逻辑,您永远无法并行化,例如折叠或合并排序 - 但你可以
  • 折叠不是递归函数调用。折叠可以通过递归来实现,但那是另一回事。随意提供一个具体的反例,其中显式递归是并行化的。
  • 所以现在我们已经认识到并非所有类型的递归都是相同的,这已经比你最初的陈述少了一点通用性。
  • 我不这么认为——递归和归约的概念是正交的。在这个想法之上构建了一个完整的框架:clojure.com/blog/2012/05/08/…
  • 递归调用可能发生在 Future 块内或在一些回调内,这些回调将在不同的线程上执行。
【解决方案2】:

在 Andreas 发表评论后,我将 m: Array[Array[Char]] 更改为 m: List[List[Char]],以防止对其进行任何不必要和不需要的更改。最后的循环方式是

    def reduc(f: (Int, Int) => Int, 
                  accu: Int, l: Int, u: Int, m1: List[List[Char]]):Int =
    accu + (l until u).toArray.par.reduce(f(accu, _) + f(accu, _))

并且我必须将 m 作为参数传递给每个使用的函数,因此每个函数都有自己的实例。整个代码:

    object SudokuSolver extends App{
      // The board is represented by an Array of strings (Arrays of Chars),
      val source = scala.io.Source.fromFile("./puzzle")

      val lines = source.getLines.toList             
      val m: List[List[Char]] = for (
        str <- lines;
        line: List[Char] = str.toList
      ) yield line 
      source.close()

      // For prInting m
      def printSud(m: List[List[Char]]) = {
        Console.println("")
        m map (println)
      }                                               

      Console.println("\nINPUT:")                     
      printSud(m)  

      def invalid(i:Int, x:Int, y:Int, n:Char,m1: List[List[Char]]): Boolean =
        i < 9 && (m1(y)(i) == n || m1(i)(x) == n ||
          m1(y / 3 * 3 + i / 3)(x / 3 * 3 + i % 3) == n ||
          invalid(i + 1, x, y, n, m1))

      def reduc(f: (Int, Int) => Int, accu: Int, l: Int, u: Int, 
                m1: List[List[Char]]): Int =
        accu + (l until u).toArray.par.reduce(f(accu, _) + f(accu, _))

      def search(x: Int, y: Int, accu: Int, m1: List[List[Char]]): Int = 
        Pair(x, y) match {
          case Pair(9, y) => search(0, y + 1, accu, m1) // next row
          case Pair(0, 9) => { printSud(m1); accu + 1 } // found a solution
          case Pair(x, y) =>
            if (m1(y)(x) != '0')
              search(x + 1, y, accu, m1) // place is filled, we skip it.
            else // box is not filled, we try all n in {1,...,9}
              reduc((accu: Int, n: Int) => {
                if (invalid(0, x, y, (n + 48).asInstanceOf[Char], m1))
                  accu
                else { // n fits here
                  val line = List(m1(y).patch(x, Seq((n + 48).asInstanceOf[Char]), 1))
                  val m2 = m1.patch(y, line, 1)
                  val newaccu = search(x + 1, y, accu, m2);
                  val m3 = m1.patch(y, m1(y).patch(x, Seq(0), 1), 1)
                  newaccu
                }
            }, accu, 1, 10, m1)
      }                                               

      Console.println("\n" + search(0, 0, 0, m) + " solution(s)")

    }

【讨论】:

    猜你喜欢
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2014-07-12
    • 1970-01-01
    • 1970-01-01
    • 2018-01-07
    相关资源
    最近更新 更多