【问题标题】:What are some opportunities improve performance / concurrency in the following Scala + Akka code?在以下 Scala + Akka 代码中,有哪些机会可以提高性能/并发性?
【发布时间】:2012-02-29 17:19:14
【问题描述】:

我正在寻找机会在我的 Scala 2.9 / Akka 2.0 RC2 代码中提高并发性和性能。给定以下代码:

import akka.actor._

case class DataDelivery(data:Double)

class ComputeActor extends Actor {
    var buffer = scala.collection.mutable.ArrayBuffer[Double]()

    val functionsToCompute = List("f1","f2","f3","f4","f5")
    var functionMap = scala.collection.mutable.LinkedHashMap[String,(Map[String,Any]) => Double]()  
    functionMap += {"f1" -> f1}
    functionMap += {"f2" -> f2}
    functionMap += {"f3" -> f3}
    functionMap += {"f4" -> f4}
    functionMap += {"f5" -> f5}

    def updateData(data:Double):scala.collection.mutable.ArrayBuffer[Double] = {
        buffer += data
        buffer
    }

    def f1(map:Map[String,Any]):Double = {
//    println("hello from f1")
      0.0
    }

    def f2(map:Map[String,Any]):Double = {
//    println("hello from f2")
      0.0
    }

    def f3(map:Map[String,Any]):Double = {
//    println("hello from f3")
      0.0
    }

    def f4(map:Map[String,Any]):Double = {
//    println("hello from f4")
      0.0
    }

    def f5(map:Map[String,Any]):Double = {
//    println("hello from f5")
      0.0
    }

    def computeValues(immutableBuffer:IndexedSeq[Double]):Map[String,Double] = {
        var map = Map[String,Double]()
        try {
            functionsToCompute.foreach(function => {
                val value = functionMap(function)
                function match {
                    case "f1" =>
                        var v = value(Map("lookback"->10,"buffer"->immutableBuffer,"parm1"->0.0))
                        map += {function -> v}
                    case "f2" =>
                        var v = value(Map("lookback"->20,"buffer"->immutableBuffer))
                        map += {function -> v}
                    case "f3" =>
                        var v = value(Map("lookback"->30,"buffer"->immutableBuffer,"parm1"->1.0,"parm2"->false))
                        map += {function -> v}
                    case "f4" =>
                        var v = value(Map("lookback"->40,"buffer"->immutableBuffer))
                        map += {function -> v}
                    case "f5" =>
                        var v = value(Map("buffer"->immutableBuffer))
                        map += {function -> v}
                    case _ => 
                        println(this.unhandled())
                }
            })
        } catch {
            case ex: Exception =>
              ex.printStackTrace()
        }
        map
    }

    def receive = {
      case DataDelivery(data) =>
        val startTime = System.nanoTime()/1000
        val answers = computeValues(updateData(data))
        val endTime = System.nanoTime()/1000
        val elapsedTime = endTime - startTime
        println("elapsed time is " + elapsedTime)
        // reply or forward
      case msg =>
        println("msg is " + msg)
    }

}

object Test {
    def main(args:Array[String]) {
        val system = ActorSystem("actorSystem") 
        val computeActor = system.actorOf(Props(new ComputeActor),"computeActor")
        var i = 0
        while (i < 1000) {  
            computeActor ! DataDelivery(i.toDouble)
            i += 1
        }
    }
}

当我运行它时,输出(转换为微秒)是

elapsed time is 4898
elapsed time is 184
elapsed time is 144
    .
    .
    .
elapsed time is 109
elapsed time is 103

您可以看到 JVM 的增量编译器开始运行。

我认为一个快速的胜利可能是改变

    functionsToCompute.foreach(function => {

    functionsToCompute.par.foreach(function => {

但这会导致以下经过的时间

elapsed time is 31689
elapsed time is 4874
elapsed time is 622
    .
    .
    .
elapsed time is 698
elapsed time is 2171

一些信息:

1) 我在具有 2 个内核的 Macbook Pro 上运行它。

2) 在完整版本中,函数是长时间运行的操作,循环遍历部分可变共享缓冲区。这似乎不是问题,因为从参与者的邮箱中检索消息正在控制流程,但我怀疑这可能是增加并发性的问题。这就是我转换为 IndexedSeq 的原因。

3) 在完整版中,functionsToCompute 列表可能会有所不同,因此并非 functionMap 中的所有项都必须被调用(即)functionMap.size 可能远大于 functionsToCompute.size

4) 函数可以并行计算,但结果映射必须在返回前完成

一些问题:

1) 如何让并行版本运行得更快?

2) 在哪里添加非阻塞和阻塞期货?

3) 将计算转发给另一个参与者有什么意义?

4) 提高不变性/安全性的机会有哪些?

谢谢, 布鲁斯

【问题讨论】:

  • 我不确定你对你的答案做了什么......但第 4 点对我来说很有趣。看起来您可以在这里充分利用 akka.dispatch.Future.sequence。创建一个正在执行计算的 Future 列表,并使用序列将其转换为结果列表中的 Future。当未来返回时,将结果折叠到您需要的聚合地图/列表/容器中。
  • @DerekWyatt。在完整版中,答案将传递给另一个演员。感谢您的评论。
  • 为什么每个功能没有一个演员?
  • @ViktorKlang。我想这是可能的,但我需要将所有函数评估的结果收集到一个映射中,然后传递给另一个参与者。 Futures 的 fork-join 功能不适合这个吗?不幸的是,我还没有足够的 Futures 经验,而且我不清楚您的建议将如何考虑到这一要求。能详细点吗?
  • @ViktorKlang。我想我需要花一些时间阅读 Akka 2.0 文档来编写 Futures...

标签: scala concurrency immutability akka par


【解决方案1】:

根据要求提供示例(抱歉延迟...我没有关于 SO 的通知)。

Akka 文档Section on 'Composing Futures' 中有一个很好的示例,但我会根据您的情况为您提供一些更适合您的内容。

现在,在阅读完本文后,请花一些时间阅读 Akka 网站上的教程和文档。您错过了这些文档将为您提供的许多关键信息。

import akka.dispatch.{Await, Future, ExecutionContext}
import akka.util.duration._
import java.util.concurrent.Executors

object Main {
  // This just makes the example work.  You probably have enough context
  // set up already to not need these next two lines
  val pool = Executors.newCachedThreadPool()
  implicit val ec = ExecutionContext.fromExecutorService(pool)

  // I'm simulating your function.  It just has to return a tuple, I believe
  // with a String and a Double
  def theFunction(s: String, d: Double) = (s, d)
  def main(args: Array[String]) {
    // Here we run your functions - I'm just doing a thousand of them
    // for fun.  You do what yo need to do
    val listOfFutures = (1 to 1000) map { i =>
      // Run them in parallel in the future
      Future {
        theFunction(i.toString, i.toDouble)
      }
    }
    // These lines can be composed better, but breaking them up should
    // be more illustrative.
    //
    // Turn the list of Futures (i.e. Seq[Future[(String, Double)]]) into a
    // Future with a sequence of results (i.e. Future[Seq[(String, Double)]])
    val futureOfResults = Future.sequence(listOfFutures)

    // Convert that future into another future that contains a map instead
    // instead of a sequence
    val intermediate = futureOfResults map { _.toList.toMap }

    // Wait for it complete.  Ideally you don't do this.  Continue to
    // transform the future into other forms or use pipeTo() to get it to go
    // as a result to some other Actor.  "Await" is really just evil... the
    // only place you should really use it is in silly programs like this or
    // some other special purpose app.
    val resultingMap = Await.result(intermediate, 1 second)
    println(resultingMap)

    // Again, just to make the example work
    pool.shutdown()
  }
}

在类路径中运行此程序所需的只是akka-actor jar。 Akka 网站会告诉你如何设置你需要的东西,但这真的很简单。

【讨论】:

  • 感谢一百万的帮助。这很好,解决了我的大部分问题。
  • 这是很酷的东西。我用 onComplete 替换了 Await,效果很好。接下来我将尝试 pipeTo()。
  • 如果我想在调用 pipeTo() 之前组合一个由两个中间期货组合而成的期货,我会做类似以下的事情吗? val combine = Future {Map("key1"->intermediate, "key2" -> intermediate2)}
  • SO 不再适合这个了...前往 Akka 邮件列表:groups.google.com/group/akka-user
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-05
  • 2016-06-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多