简体   繁体   中英

Parallelizing recursive branching computations in scala using futures

I am trying to parallelize a SAT solver in Scala using Futures.

The algorithm for solving a SAT problem is loosely like (pseudo-code):

def has_solution(x):
    if x is a solution:
        return true
    else if x is not a solution:
        return false
    else:
        x1 = left_branch(x)
        x2 = right_branch(x)
        return has_solution(x1) or has_solution(x2)

So I see an opportunity to parallelize the computation whenever I branch the problem.

How can I do this with Futures? I need to wait for results from has_solution(x1) and has_solution(x2), and:

  1. Return true as soon as either branch returns true
  2. Return false if both branches return false

My current approach is the following:

object DPLL {
  def apply(formula: Formula): Future[Boolean] = {
    var tmp = formula

    if (tmp.isEmpty) {
      Future { true }
    } else if (tmp.hasEmptyClause) {
      Future { false }
    } else {

      for (unitClause <- tmp.unitClauses) tmp = tmp.propagateUnit(unitClause);
      for (pureLiteral <- tmp.pureLiterals) tmp = tmp.assign(pureLiteral);

      if (tmp.isEmpty())
        Future { true }
      else if (tmp.hasEmptyClause)
        Future { false }
      else {
        val nextLiteral = tmp.chooseLiteral

Here is where branching takes place and where I'd like to wait for the computations as described above:

        for (f1 <- DPLL(tmp.assign(nextLiteral)); 
             f2 <- DPLL(tmp.assign(-nextLiteral)))
          yield (f1 || f2)
      }
    }
  }
}

This looks wrong when I run it because I can never achieve full use of my cores (8).

I have an intuition I should not be using futures for this kind of computation. Maybe futures are suited just for asynchronous computations. Should I try some lower-level threading or actor-based approach for this? Thanks.

This code works sequentially because of for block! Computation of f2 starts after computation of f1 finished.

for {
  f1 <- DPLL(tmp.assign(nextLiteral))
  f2 <- DPLL(tmp.assign(-nextLiteral))
} yield f1 || f2

Above block translates to following flatMap/map sequence and what flatMap/map does is to run the function after value is present.

DPLL(tmp.assign(nextLiteral)).flatMap(f1 =>
  DPLL(tmp.assign(-nextLiteral)).map(f2 =>
    f1 || f2)

One easy trick for starting computations in parallel is assigning them to a value and access that value in for comprehension

val comp1 = DPLL(tmp.assign(nextLiteral))
val comp2 = DPLL(tmp.assign(-nextLiteral))
for {
  f1 <- comp1
  f2 <- comp1
} yield f1 || f2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM