Scala有状态的演员,递归调用比使用vars更快?

时间:2011-04-28 23:13:26

标签: performance scala actor

以下示例代码。我有点好奇为什么MyActor比MyActor2更快。 MyActor以递归方式调用process / react并在函数参数中保持状态,而MyActor2将状态保持在vars中。 MyActor甚至有额外的开销状态,但仍然运行得更快。我想知道是否有一个很好的解释,或者我可能做了一些“错误的”。

我意识到性能差异并不显着,但它存在且一致的事实让我很好奇这里发生了什么。

忽略前两次作为预热,我得到:

MyActor: 559 511 544 529

VS

MyActor2: 647 613 654 610

import scala.actors._

object Const {
  val NUM = 100000
  val NM1 = NUM - 1
}

trait Send[MessageType] {
  def send(msg: MessageType)
}

// Test 1 using recursive calls to maintain state

abstract class StatefulTypedActor[MessageType, StateType](val initialState: StateType) extends Actor with Send[MessageType] {
  def process(state: StateType, message: MessageType): StateType

  def act = proc(initialState)

  def send(message: MessageType) = {
    this ! message
  }

  private def proc(state: StateType) {
    react {
      case msg: MessageType => proc(process(state, msg))
    }
  }
}

object MyActor extends StatefulTypedActor[Int, (Int, Long)]((0, 0)) {
  override def process(state: (Int, Long), input: Int) = input match {
    case 0 =>
      (1, System.currentTimeMillis())
    case input: Int =>
      state match {
        case (Const.NM1, start) =>
          println((System.currentTimeMillis() - start))
          (Const.NUM, start)
        case (s, start) =>
          (s + 1, start)
      }
  }
}

// Test 2 using vars to maintain state

object MyActor2 extends Actor with Send[Int] {
  private var state = 0
  private var strt = 0: Long

  def send(message: Int) = {
    this ! message
  }

  def act =
    loop {
      react {
        case 0 =>
          state = 1
          strt = System.currentTimeMillis()
        case input: Int =>
          state match {
            case Const.NM1 =>
              println((System.currentTimeMillis() - strt))
              state += 1
            case s =>
              state += 1
          }
      }
    }
}


// main: Run testing

object TestActors {
  def main(args: Array[String]): Unit = {
    val a = MyActor
    //    val a = MyActor2
    a.start()
    testIt(a)
  }

  def testIt(a: Send[Int]) {
    for (_ <- 0 to 5) {
      for (i <- 0 to Const.NUM) {
        a send i
      }
    }
  }
}

编辑:根据Vasil的回复,我删除了循环并再次尝试。然后基于vars的MyActor2超越了,现在可能会快10%左右。所以...经验教训是:如果你确信你最终不会有堆积溢出的积压消息,并且你想要挤出每一个小小的表现......不要使用循环而只是调用行为()方法递归。

更改MyActor2:

  override def act() =
    react {
      case 0 =>
        state = 1
        strt = System.currentTimeMillis()
        act()
      case input: Int =>
        state match {
          case Const.NM1 =>
            println((System.currentTimeMillis() - strt))
            state += 1
          case s =>
            state += 1
        }
        act()
    }

3 个答案:

答案 0 :(得分:3)

这样的结果是由您的基准测试的细节引起的(许多小消息填充演员的邮箱比他们处理它们更快)。

通常,the workflow of react如下:

  1. Actor扫描邮箱;
  2. 如果找到消息,则为schedules the execution;
  3. 当调度完成时,或者当邮箱中没有邮件时,actor挂起(Actor.suspendException被抛出);
  4. 在第一种情况下,当处理程序完成处理消息时,执行直接进入react方法,并且,只要邮箱中有大量消息,actor立即将下一条消息安排到执行,并且只在暂停之后。

    在第二种情况下,loop schedules the execution of react是为了防止堆栈溢出(可能是您的Actor#1的情况,因为process中的尾递归未被优化),因此,执行不会立即进行react,如第一种情况。这就是毫无疑问的损失。


    更新(取自here):

      

    使用循环而不是递归反应   有效地使数量翻倍   线程池必须具有的任务   执行以完成   相同数量的工作,反过来   使它成为任何开销   调度程序更加明显   使用循环。

答案 1 :(得分:0)

在黑暗中疯狂刺伤。这可能是由于反应引发的异常以便撤离循环。异常创建非常繁重。但是我不知道它经常这样做,但应该可以检查一个捕获和一个计数器。

答案 2 :(得分:0)

测试开销在很大程度上取决于存在的线程数(尝试仅使用一个scala -Dactors.corePoolSize=1的线程!)。我发现很难弄清楚差异的确切位置;唯一真正的区别在于,在一种情况下,您使用loop,而在另一种情况下则不使用scala.actors.Scheduler$.impl。循环做了相当多的工作,因为它使用“andThen”重复创建函数对象而不是迭代。我不确定这是否足以解释差异,特别是考虑到ExceptionBlob和{{1}}的大量使用。