Concurrency with Scala (was Concurrency with Erlang)

Steve Vinosky has written some really interesting articles on Erlang concurrency and Erlang reliability. I think i am going to buy that book on Erlang he recommends.

After i read his article on concurrency i wondered if the same (contrived but instructive) example he presents could be written in Scala and how it would compare, if anything it is a good exercise in learning.

Here is the simple non-concurrent (tail call) recursive pow function in Scala:

def pow(n: int, m: int): BigInt = {
    def _pow(m: int, acc: BigInt): BigInt  = m match {
        case 0 => acc
        case _ => _pow(m - 1, acc \* n)
    }
     _pow(m, 1)
}

and here is the concurrent cpow function using Scala's event-based actors:

def cpow(n: int, m: int): BigInt = {
    val actors = for (_ <- List.range(0, m)) yield
actor { react { case x: int => sender ! x } }
    actors foreach ( a => a ! n )
    (actors foldLeft BigInt(1)) { (t , a) => self ? match { case x: int => t \* x } }
}    

The value actors, which is a list of m actors, is obtained using list comprehension. As in Steve's example each actor just pings back the value it receives from the sender. Then a message is sent to each actor with the value of n. Finally all the values received from the actors are multipled together (as BigInt types) using the foldLeft function. Rather elegant! and similar in size to the Erlang code.

I measured the time it took for both functions to calculate 502000 on my Acer Ferrari 3400 laptop running Solaris, Java SE 5, and Scala 2.6.0. The pow function took 17.86 ms and the cpow function took 81.28 ms.

Below I re-iterate one of Steve's final points with a slight modification:

..., many developers are comfortable with OO programming. I’d like to advise such developers not to let Erlang’s or Scala's functional nature scare you away, ...

Given that Scala is a hybrid object-oriented and functional programming language it can enable developers to smoothly transition between the imperative and functional programming styles in  small manageable steps i.e. Scala is less scary than it might initially appear.

Comments:

So the concurrent version is horribly slow? What a surprise. Even if this problem was suited for a concurrent implementation, actors would be the wrong way to do it. Scala has futures and other mechanisms which don't have as much overhead as actors/processes

Posted by Asd on November 01, 2007 at 09:44 AM CET #

Asd, slower it is, although when you crank up the power the two converge because BigInt multiplication starts to dominate. Still, i expected things to be even slower given what i perceived to be the overhead of event-based actors.

However, that was not the main point of the blog entry, perhaps i should have been clearer upfront on the caveats. As Steve says:

"The example in Figure 1 is admittedly contrived, but I’ll use it anyway because it manages to cram some important concepts into just a few lines of code."

In that context i found Steve's contrived example informative on Erlang and i found it informative doing the same exercise in Scala.

Posted by Paul Sandoz on November 01, 2007 at 10:02 AM CET #

Actually, the more I read up on Scala, the more scared I got. Basically, at the typing system.

Posted by Patrick Mueller on November 01, 2007 at 10:11 AM CET #

Patrick, LOL! do you have an example handy to explain some aspects of the fear?

Posted by Paul Sandoz on November 01, 2007 at 10:17 AM CET #

I think I gave up when I saw the covariance / contravariance stuff. I reasoned that this stuff is beyond mere mortals, at that point.

Posted by Patrick Mueller on November 01, 2007 at 10:52 AM CET #

Patrick, :-) ah yes, i have only given this a brief look so far, i found it rather "opaque". Fortunately i have been playing around quite happily (and naively?) without bumping into this, yet!

Posted by Paul Sandoz on November 01, 2007 at 11:01 AM CET #

I'm not sure the comparison of times for the two versions is particularly illuminating on a small (presumably single) number of cores. More interesting would be to look at how it scales on these two versions as you add cores. Presumably the concurrent version would scale better, I would expect?

Posted by Alex Miller on November 01, 2007 at 10:42 PM CET #

Alex, i agree, it would be interesting to show how it scales using multiple cores. I would like to investigate this if i get my paws on a multi-core marchine. Although taking Asd's valid point i think it would be better to work on a less contrived but still simple example.

I found it interesting to get an idea of the overhead of actors. My conclusion is that event-based actors are reasonably cheap to create (as are spawning light weight processes in Erlang). So in the future when/if i use actors i will be less concerned about creating loads of 'em (which i was when writing the Mandel example i have referred to in previous blogs).

Posted by Paul Sandoz on November 02, 2007 at 03:32 AM CET #

I would not suppose Scala implementation of Actor model is as Scalable as Erlang.

Posted by Carfield Yim on November 02, 2007 at 11:42 AM CET #

Carfield, i would not suppose that either way :-), need more data!

Although... :-) FWIW the ratio of times in Steve's article was 3.78 and the ratio i measured for Scala was 4.55. Since Steve made a mistake in his original article and the pow function was not a tail call i am wildly guessing that if the non-process tail call pow function was measured it would be faster thus increasing the ratio.

Posted by Paul Sandoz on November 02, 2007 at 11:57 AM CET #

I only guess about this, may be that is wrong. The reason I make that guess is Scala base on JVM. Which is design to use threads but not design for a lot of light weight process and message passing.

On the other hand, Implementation of Erlang (may be compiler? Or Erlang also have VM?) design and tune the light weight process from ground up for more than 20 years, JVM need to while to pick that up.

Posted by Carfield Yim on November 02, 2007 at 12:16 PM CET #

Carfield, a fair point, although, to counter point, JVM implementations are now good at handling large quantities of short lived objects (e.g. short-lived messages and LW processes) and for any parallel event-processing implementation one needs a good thread pool. A paper referenced here:
http://lamp.epfl.ch/~phaller/doc/haller07coord.pdf
is interesting, especially section 7 on the preliminary results.
But, when you read section 4 it gets into some limitations on JVMs:
"The execution of the closure is piggy-backed on the thread of the sender. When the receiving closure terminates, control is returned to the sender by throwing a special exception that unwinds the receiver's call stack."
my previous experience is throwing exceptions can be expensive (and not sure what is it like on SE 6 impls), and:
"on the JVM there is no safe way for library code to find out if a thread is blocked."

Posted by Paul Sandoz on November 05, 2007 at 02:12 AM CET #

Post a Comment:
  • HTML Syntax: NOT allowed
About

sandoz

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today