我对使用scala和akka编程相对较新,我尝试使用akka actor为n-queens问题编写解决方案。 不幸的是,我的想法效果不好:计算所有内容的时间更长 解决方案与顺序方法相比,程序永远不会终止。但是,一些正确的解决方案将打印到控制台。 这是我的代码:
case class Work(list: List[Point])
case class Reply(list: List[Point])
class QueenWorker(val master: ActorRef) extends Actor {
override def preStart() = {
println("new worker")
}
def receive = {
case Work(list) =>
val len = list.length
if (len < size) {
val actors = for (
i <- 0 until size if (!list.exists(_.y == i))
) yield (context.actorOf(Props(new QueenWorker(master))), i)
actors.foreach { case (actor, pos) => actor ! Work(list :+ (new Point(len, pos))) }
} else {
if (check(list)) { //check() checks whether the solution is valid
master ! Reply(list)
println("solution found!")
}
//else sender ! Reply(List[List[Point]]())
}
//context.stop(self) //when do I have to use it?
//println("worker stopped - len "+len)
}
}
class QueenMaster extends Actor {
override def preStart() = {
println("preStart")
context.actorOf(Props(new QueenWorker(self))) ! Work(List[Point]())
}
def receive = {//print solution to console
case Reply(list) =>
for (x <- 0 until size) {
for (y <- 0 until size) {
if (list.exists(p => p.x == x && p.y == y)) print("x ") else print("o ")
}
println()
}
println()
}
}
def runParallel {
val system = ActorSystem("QueenSystem")
val queenMaster = system.actorOf(Props[QueenMaster])
}
我的目的是为每个新的回溯迭代创建一个新的actor。如果演员找到了有效的解决方案,则会将其发送给将其打印到控制台的主人。
提前致谢!
答案 0 :(得分:0)
经过一些调整后,我设法运行你的程序并获得size=8
的结果。出于调试目的,在runParallel
:
readLine()
system.shutdown()
程序完成后 - 按Enter键 - 这将导致调用shutdown
方法,程序将退出。
我在8核i7 2.2Ghz机器上运行 - 所以你的结果可能会有所不同。
关于性能,这几乎是一个效率低下的解决方案,原因如下: 在回溯过程的每一步 - 即每个尝试过的部分解决方案 - 你正在创建和演员并做一个简单的循环,为下一个提出的解决方案创造更多的角色。
现在,在Akka中创建一个演员的速度非常快但是我敢说在你的情况下创建一个演员的开销比它正在做的实际工作更大(甚至可能是一个数量级) - 我没有&# 39; t测试了这个,但很可能。
此处分析的部分解决方案的数量是电路板尺寸的指数。那就是你创造了一个指数数量的演员 - 这是一个很大的开销,你肯定会失去通过并行计算解决方案所获得的任何优势。
我们怎样才能做得更好?嗯,首先,我们可以减少一些演员。
让我们创建一个QueenWorker
个演员的固定池(与您计算机中的核心数相同的数字),并将它们放在SmallesMailboxRouter后面。
当您的员工检查处理过的Work
消息中的当前解决方案而不是创建新的参与者时,他们会将新建议的解决方案发送到路由器,路由器会将这些新的Work
消息发送给它路线,以统一的方式。
现在,工人演员将处理更多部分解决方案而不仅仅是一个解决方案。这是比以前更好的actual_work / akka_housekeeping比率。
我们能做得更好吗?如果你对问题的建模有点不同,我想我们可以。在女王问题中,一般来说,在回溯中你正在探索一个部分解决方案的树。 在算法的每个步骤中,当您从现有算法生成更多部分解决方案时,您将分支您的树。你的方法的问题在于,当你在算法中进行分支时,你也在并发流程中进行分支 - 我在这里指的是你发送一条新的工作消息以便由另一个actor处理。这很好,但如果你添加数字,你会发现这是一个很大的开销,不会让你获得任何时间优势。
这是因为Work
任务的粒度非常小。你在发送消息之间做的工作很少 - 这会影响你的表现。在生成新的Work
消息之前,您可以让工作人员探索更大的部分解决方案。
例如,如果你有一个8核的CPU,并且问题有size=8
那么你最好创建8个工作人员,并给工人K提供任务来计算让女王坐在列上的解决方案K在第一行。现在每个工作人员只执行一个Work
任务,大约占总工作量的1/8 - 这将产生更好的结果。
关于期货的解决方案 - 你当然可以这样做,但我认为程序时间与具有固定的演员池的演员解决方案大致相同。但我鼓励你尝试,因为我可能错了。
答案 1 :(得分:0)
非常感谢您的回答。 你是对的 - 主要的问题是找到一个解决方案,有效地划分问题,但不会产生太多的演员或消息。 即使是SmallesMailboxRouter解决方案也没有成功,因为我创建了太多消息。 但是,我找到了一个解决方案,为第一个女王的每个位置创建一个新的actor(就像你建议的那样)这实际上比顺序处理更快,缺点是问题“只”被分成n(n = size国际象棋棋子)(但这比什么都没有!) 这是一些代码:
class QueenActor extends Actor {
override def preStart {
val start = System.currentTimeMillis()
val listOfFutures = (for (i <- 0 until size) yield Future { doIt2(List[Point](new Point(0, i))) }).toList
val futureOfLists = Future.sequence(listOfFutures)
futureOfLists.onSuccess {
case lists =>
var solutions = 0
for (list <- lists.flatten) {
solutions += 1
println(solutions + ":")
for (x <- 0 until size) {
for (y <- 0 until size) {
if (list.exists(p => p.x == x && p.y == y)) print("x ") else print("o ")
}
println()
}
println()
}
println("Time: " + (System.currentTimeMillis() - start))
System.exit(0)
}
}
def receive = {
case _ =>
}
}
def runParallelFutures {
val system = ActorSystem("QueenSystem")
val queenMaster = system.actorOf(Props[QueenActor])
}
我只需要Actor,因为没有一个程序会立即终止,尽管期货尚未处理。然而,这个解决方案似乎并不是非常“优雅” - 也许有一个更好的解决方案。但我想我不能使用“Await”,因为我必须指出超时(事先我不知道)
再次,非常感谢!