为什么并行范围处理比基于Future的并行处理需要更多的时间(N-queens示例)?

时间:2016-07-08 21:50:48

标签: scala parallel-processing n-queens

我提出了两个并行解决方案,以便尽快找到针对N皇后问题的解决方案。

第一个使用期货

import scala.collection.immutable.HashSet
import scala.concurrent.{Await, Future, Promise}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

/**
  * Created by mikel on 17/06/16.
  */
object Queens2 extends App {
  val time = System.currentTimeMillis()
  val boardSize = 200

  def firstResult(): Future[List[Int]] = {
    def iterate(solution: Vector[(Int, Int)], remainingElements: Set[Int], invalidSum: HashSet[Int], invalidMinus: HashSet[Int]): Stream[List[(Int, Int)]] = {
      def isSafe(queens: Vector[(Int, Int)], queen: Int): Boolean = {
        !invalidSum.contains(queens.size + queen) && !invalidMinus.contains(queens.size - queen)
      }

      if (solution.size == boardSize)
        Stream(solution.toList)
      else {
        for {
          nextQueen <- remainingElements.toStream if isSafe(solution, nextQueen)
          res <- iterate(solution :+(solution.size, nextQueen), remainingElements - nextQueen, invalidSum + (solution.size + nextQueen), invalidMinus + (solution.size - nextQueen))
        } yield (res)
      }
    }

    val promise = Promise[List[Int]]()
    val allElements = (0 until boardSize).toSet

    val range = (0 until boardSize)
    range.foreach(pos => {
      // HERE we parallelize the execution
      Future {
        promise.trySuccess(iterate(Vector((0, pos)), allElements - pos, HashSet(pos), HashSet(-pos)).map(_.map(_._2)).head)
      }
    }
    )

    promise.future
  }

  val resFuture = firstResult()
  resFuture.onSuccess { case res =>

    println("Finished in: " + (System.currentTimeMillis() - time))
    println(res)
    System.exit(0)
  }

  Await.result(Promise().future, Duration.Inf)
}

另一个使用ParRange

import scala.collection.immutable.HashSet
import scala.concurrent.{Await, Future, Promise}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
/**
  * Created by mikel on 17/06/16.
  */
object Queens extends App {
  val time = System.currentTimeMillis()
  val boardSize = 200

  def firstResult(): Future[List[Int]] = {
    def iterate(solution: Vector[(Int, Int)], remainingElements: Set[Int], invalidSum: HashSet[Int], invalidMinus: HashSet[Int]): Stream[List[(Int, Int)]] = {
      def isSafe(queens: Vector[(Int, Int)], queen: Int): Boolean = {
        !invalidSum.contains(queens.size + queen) && !invalidMinus.contains(queens.size - queen)
      }

      if (solution.size == boardSize)
        Stream(solution.toList)
      else {
        for {
          nextQueen <- remainingElements.toStream if isSafe(solution, nextQueen)
          res <- iterate(solution :+(solution.size, nextQueen), remainingElements - nextQueen, invalidSum + (solution.size + nextQueen), invalidMinus + (solution.size - nextQueen))
        } yield (res)
      }
    }

    val promise = Promise[List[Int]]()
    Future {
      val allElements = (0 until boardSize).toSet

      // HERE we parallelize the execution
      val range = (0 until boardSize).par
      range.foreach(pos => {
        promise.trySuccess(iterate(Vector((0, pos)), allElements - pos, HashSet(pos), HashSet(-pos)).map(_.map(_._2)).head)
      }
      )
    }
    promise.future
  }

  val resFuture = firstResult()
  resFuture.onSuccess { case res =>

    println("Finished in: " + (System.currentTimeMillis() - time))
    println(res)
    System.exit(0)
  }

  Await.result(Promise().future, Duration.Inf)
}

用200尺寸的电路板执行这两个程序后,我用第一种方法获得了更快的解决方案(显然在一段时间后第二个解决方案中的并行化水平下降),任何人都知道为什么会发生这种情况?

1 个答案:

答案 0 :(得分:2)

因此,第二个代码段的固定版本运行得足够快:

  import java.util.concurrent.Executors

  import scala.collection.immutable.HashSet
  import scala.concurrent.duration._
  import scala.concurrent.{Await, ExecutionContext, Future, Promise}
  /**
    * Created by mikel on 17/06/16.
    */
  object Queens extends App {
    val time = System.currentTimeMillis()
    val boardSize = 200

    def firstResult(): Future[List[Int]] = {
      def iterate(solution: Vector[(Int, Int)], remainingElements: Set[Int], invalidSum: HashSet[Int], invalidMinus: HashSet[Int]): Stream[List[(Int, Int)]] = {
        def isSafe(queens: Vector[(Int, Int)], queen: Int): Boolean = {
          !invalidSum.contains(queens.size + queen) && !invalidMinus.contains(queens.size - queen)
        }

        if (solution.size == boardSize)
          Stream(solution.toList)
        else {
          for {
            nextQueen <- remainingElements.toStream if isSafe(solution, nextQueen)
            res <- iterate(solution :+(solution.size, nextQueen), remainingElements - nextQueen, invalidSum + (solution.size + nextQueen), invalidMinus + (solution.size - nextQueen))
          } yield (res)
        }
      }

      val futureExecutor = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(20))
      val promise = Promise[List[Int]]()
      Future({
        val allElements = (0 until boardSize).toSet

        // HERE we parallelize the execution
        val range = (0 until boardSize).par
        range.foreach(pos => {
          promise.trySuccess(iterate(Vector((0, pos)), allElements - pos, HashSet(pos), HashSet(-pos)).map(_.map(_._2)).head)
        }
        )
      })(futureExecutor)
      promise.future
    }

    val resFuture = firstResult()
    resFuture.onSuccess({ case res =>

      println("Finished in: " + (System.currentTimeMillis() - time))
      println(res)
      System.exit(0)
    })(scala.concurrent.ExecutionContext.Implicits.global)


    Await.result(Promise().future, Duration.Inf)
  }

正如您所看到的,我已经引入了单独的执行器来等待结果并计算您的未来。为了使它更明显我明确地说明了它们,但当然你可能会使用含义。

第二个代码段中的问题是默认执行上下文(scala.concurrent.ExecutionContext.Implicits.global)中的线程池耗尽,因此在几乎所有计算完成之前,您的承诺不会触发。

ParRange使用默认TaskSupport的全局上下文:

//...
private[parallel] def getTaskSupport: TaskSupport = new ExecutionContextTaskSupport
//...
class ExecutionContextTaskSupport(val environment: ExecutionContext = scala.concurrent.ExecutionContext.global)
extends TaskSupport with ExecutionContextTasks