AndreVanDelft / scala

The SubScript extension to the Scala programming language
http://www.subscript-lang.org/
12 stars 1 forks source link

Improve VM performance #31

Open AndreVanDelft opened 10 years ago

AndreVanDelft commented 10 years ago

The current VM processes about 10,000 actor messages per second, for a simple Sieve actor system. This may probably largely be improved by eliminating most creations and handlings of call graph messages:

Tasks now done by Activation, Deactivation, AAActivated, AAHandled may largely be implemented by recursive methods; for 1-ary and n-ary operators, and for then-else like constructs, there is a need for a continuation. The Break messages may probably completely eliminated; a break or optional break could just set a few fields values in the closest by n-ary ancestor nodes.

However, the current inefficient implementation has also merits: it allows for nice animations in the debugger.

Therefore it should be possible to implement an optimizaton that leaves the old messaging model available.

anatoliykmetyuk commented 10 years ago

I don't think recursive methods is a good thing: overhead of new call stack creation is considerable. Also, we shouldn't keep something just for the sake of debugger. Debugger is what supposed to help us, not keep us from improving thing the best way possible. It's better to change the debugger in case of such need.

AndreVanDelft commented 10 years ago

Keeping the messaging alsmost as it is now, as an option, is not meant for debugging, but for demonstration and learning. So I think it is good to strive for. Besides, I think it can be done with very few overhead and added complexity.

Recursion works very efficient in Scala; often better than plain loops. I made some simple test code:

def time(s:String)(task: =>Long) = {
  val ts = System.currentTimeMillis(); var r=0L; for (i<-0 to 1000) r+=task
  val te = System.currentTimeMillis()
  val d  = te - ts
  println(f"$s: $d%4d   $r")
}

def sum2(accu:Long, i:Long): Long = if(i==0) accu else sum2(accu+i, i-1)
def sum(i:Long): Long = sum2(0,i)

def max = 100000
;; time("  rec"){sum(max)
}; time("  for"){var r=0L; for(i<-0 to max) r += i; r
}; time("while"){var r=0L; var i=0L; while(i<=max) {r += i; i+=1}; r}

Running these yields:

scala> ;; time("  rec"){sum(max)
     | }; time("  for"){var r=0L; for(i<-0 to max) r += i; r
     | }; time("while"){var r=0L; var i=0L; while(i<=max) {r += i; i+=1}; r}
  rec:   63   5005050050000
  for:  494   5005050050000
while:   68   5005050050000

Recursion is the winner

anatoliykmetyuk commented 10 years ago

This is due to 'tail recursion'. If you write sum2 func as follows:

sum2(x: Long, i: Long): Long = if(i == 0) x else sum2(x + i, i - 1) + 1

things will go differently

AndreVanDelft commented 10 years ago

I think that tail recursion will be possible for part of the cases where now messages are handled.

AndreVanDelft commented 10 years ago

Another issue: the current CommonScriptExecutor has a main loop of handling messages; there is a call to "wait" in case there is nothing to do. This lasts until a notify, when some event handling code fragment gets kicked by an external event, or when a threaded code fragment becomes ready.

The contents of this loop is also run iteratively from SSARunnerV1Scheduler. There the loop is implemented in this method:

  def doScriptSteps_loop: Unit = {
    doScriptSteps
    if (executor.hasActiveProcesses) {
      system.scheduler.scheduleOnce(scheduledTaskDelay)(doScriptSteps_loop)
    }
  }

This also requires an equivalent of the wait-notify solution:

The VM should offer a generic solution that supports any of such strategies with wait/notify and scheduler.scheduleOnce.

anatoliykmetyuk commented 10 years ago

I think, this can be done with the observer-subscriber pattern. As a part of the refactoring that I'm doing, I've implemented such a pattern (particularly, Debugger now becomes more general Listener - we may want to listen not only for the purpose of debugging). So we can just register a new listener for messageQueued that will fire a desired function when message is queued.

But, I don't think that currently it is an issue. This looping code is not too heavy, it can't cause too much performance impact. Our main focus should be on SubScript VM by itself, not the runner.