并行化a使用视图(Scala)

时间:2012-05-10 13:33:50

标签: scala

我对Scala很新!但是,我有Euler Problem 4的以下工作解决方案,我想使用par,只是为了看看我是否可以这样做:

import scala.math

object Problem4 {
    def isPalindrome(x: Int): Boolean = {
        val string = x.toString
        string.reverseIterator.sameElements(string.iterator)
    }

    def getPairs(minimum: Int, maximum: Int) = {
        for (i <- minimum to maximum view;
             j <- minimum to maximum view)
             yield (i, j)
    }

    def getAnswer(numberOfDigits: Int): Int = {
        val maximum = math.pow(10, numberOfDigits).toInt
        val minimum = math.pow(10, numberOfDigits - 1).toInt
        val products = for {
                           pair <- getPairs(minimum, maximum)
                           product = pair match { case (i, j) => i * j } 
                           if isPalindrome(product)
                       } yield product
        products.par.max
    }

    def main(args: Array[String]) {
        val answer = getAnswer(4)
        println("Problem 4 answer: %s".format(answer))
    }
} // object Problem4

Project Euler 4要求输入3位数字,我注意到在我的电脑上找到4位数字的答案需要63秒才能在我的双核系统上使用一个处理器。尽管将par应用于for表达式的末尾。

如何使用par将其并行化?理想情况下,我想找到4位数的答案需要30-40秒。谢谢!

编辑:我非常确定getPairs会返回View

    scala>     def getPairs(minimum: Int, maximum: Int) = {
         |         for (i <- minimum to maximum view;
         |              j <- minimum to maximum view)
         |              yield (i, j)
         |     }
    getPairs: (minimum: Int, maximum: Int)scala.collection.SeqView[(Int, Int),Seq[_]]

此外,将par添加到getPairs调用会返回警告,仍然只使用我的某个处理器,并导致java.lang.OutOfMemoryError: Java heap space例外:

[info] Loading project definition from M:\programming\testdriveneuler\src\problem4\project
[info] Set current project to euler (in build file:/M:/programming/testdriveneuler/src/problem4/)
[info] Compiling 1 Scala source to M:\programming\testdriveneuler\src\problem4\target\scala-2.9.2\classes...
[warn] M:\programming\testdriveneuler\src\problem4\src\main\scala\Problem4.scala:39: `withFilter' method does not yet exist on scala.collection.parallel.ParSeq[((Int, Int), Int)], using `filter' method instead
[warn]                            pair <- getPairs(minimum, maximum).par
[warn]                                 ^
[warn] one warning found
编辑:我明确有兴趣计算出2个4位数字乘积的欧拉问题4的答案。作为参考,答案是99000099

3 个答案:

答案 0 :(得分:3)

如此复杂。它可以用2个函数

完成
def isPalindrome(x: Int) = x.toString sameElements x.toString.reverse

def products(min: Int, max: Int) = for { 
  x <- min to max par; 
  y <- min to max par; 
  if isPalindrome(x * y) 
} yield (x, y, x * y)

scala> products(100, 999).maxBy(_._3)
res0: (Int, Int, Int) = (913,993,906609)

更新

(min to max).view返回SeqView,表示懒惰的集合版本。 (min to max).view.par返回ParSeq并行收集。换句话说,在惰性序列上调用par会强制它进行评估。因此,在这种情况下,你应该选择懒惰和平行。当您从SeqView转移到ParSeq时,很难说会执行哪些转化,但这种不必要的复杂性导致OutOfMemoryError

UPDATE2

是的,for只是对集合的高阶操作的语法糖。 Des for循环的Desugared版本将类似于:

(min to max par) flatMap { x =>
  (min to max par)
    .filter(y => isPalindrome(x * y))
    .map(y => x * y)
}

答案 1 :(得分:1)

在调用getPairs时添加.par;

pair <- getPairs(min, max).par

我怀疑(但我的手机无法确定)getPairs方法没有返回视图;因此你的for循环执行你的计算。

验证这一点的一种简单方法是省略最后一行(即products.par.max - for循环的评估),看看你的程序是否仍然执行计算。

答案 2 :(得分:1)

你可以这样并行化:

  def isPalindrome (n: Int) = n.toString == n.toString.reverse
  val R = 100 until 1000
  val xs = for (i <- R; j <- R) yield i * j
  val pals = xs.par filter isPalindrome
  println (pals max)

(忽略.par非平行)。但是我发现并行版本在我的双核机器上慢了3-4倍。有时并行化的开销是不值得的。

编辑:对于咯咯笑,这是使用Akka的版本(基于Pi calculation tutorial)。它的性能略快于使用并行集合,如4e6的答案(在我的机器上 8.0s vs 9.1s ),尽管如果你删除了该解决方案,性能几乎相同.par在第二台发电机上。

import akka.actor._
import akka.routing.RoundRobinRouter

sealed trait Euler4Message
case object Calculate extends Euler4Message
case class Work(range1: Seq[Int], range2: Seq[Int]) extends Euler4Message
case class Result(value: Int) extends Euler4Message
case class FinalResult(value: Int, duration: Long)

class Worker extends Actor {

  def calculate(r1: Seq[Int], r2: Seq[Int]): Int = {
    def isPalindrome(x: Int) = {
      val s = x.toString
      s.reverseIterator.sameElements(s.iterator)
    }
    val pals = for (i <- r1; j <- r2; if isPalindrome(i * j)) yield i * j
    pals.max
  } 

  def receive = { case Work(r1, r2) => sender ! Result(calculate(r1, r2)) }   
}

class Master(nrOfDigits: Int, nrOfWorkers: Int, chunkSize: Int) extends Actor {

  var nrOfResults: Int = 0
  var maxResult = 0
  var sentAll = false
  var nrMessages = 0

  val start: Long = System.currentTimeMillis
  val min = math.pow(10, nrOfDigits - 1).toInt
  val max = math.pow(10, nrOfDigits).toInt 
  val range = min until max
  val workerRouter = 
    context.actorOf(Props[Worker].withRouter(RoundRobinRouter(nrOfWorkers)))

  def receive = {
    case Calculate =>
      for (i <- range.grouped(chunkSize)) { 
        // grouped produces an Iterator, so is 'lazy'
        workerRouter ! Work(i, range)
        nrMessages += 1
      }
      sentAll = true

    case Result(value) =>
      if (value > maxResult) maxResult = value
      nrOfResults += 1
      if (sentAll && nrOfResults == nrMessages) {
         println("Result = "+ maxResult 
                +"\nTime in ms: "+ (System.currentTimeMillis - start))
         context.system.shutdown()
      }
  }
}

object Euler4 extends App {
  val master =  ActorSystem().actorOf(Props(new Master(4, 4, 50)))
  master ! Calculate
}

演员的好处是你可以使用命令式代码并且仍然可以获得并行性。因此,在下面的工作者演员中交换calculate方法,整个事情在 1.0s (8倍改进)中完成。

我发现它在较大的块大小(尝试1000)时运行速度最快,并且确保您至少拥有与处理器一样多的工作人员。

  def calculate(r1: Seq[Int], r2: Seq[Int]): Int = {
    def isPalindrome(x: Int) = {
      val s = x.toString
      s.reverseIterator.sameElements(s.iterator)
    }
    var max = 0
    // count down so that we don't have to check if palindrome so often
    var i = r1.last
    while (i >= r1.head) {
      // for some reason counting down here increases the run-time :/
      var j = r2.head
      while (j <= r2.last) {
        val r = i * j
        if (r > max && isPalindrome(r)) max = r
        j += 1
      }
      i -= 1
    }
    max
  }