Java Actors with Kilim

16

I finally got around to porting my actor process ring code (Erlang, Scala) to run with the Kilim library tonight.

Kilim is a Java library for lightweight message-passing. The three main things to know about are Tasks, Mailboxes, and @pausable. In Kilim, actors are replaced with Tasks, which are just lightweight runnable objects that serves basically the same role.

Mailboxes are designed for multiple-producer, single-consumer access just like Erlang and Scala actor mailboxes. In Kilim though, Mailbox is just a class and Tasks don’t necessarily have a Mailbox or could even have more than one. Mailboxes are also generic and typed by the kind of message they should receieve. This opens up new ways to compose Tasks and Mailboxes into a broader range of structures. Messages are typically simple (potentially mutable) classes.

Finally, there is an annotation called @pausable. This is used to specify that a method can be paused, continuation-style, during pausable calls; sleep and yield are two provided hooks and the mailbox get / put methods are also pausable. @pausable is also used to mark classes for instrumentation.

That leads me to the compile-time weaver. After you’ve compiled your classes, you need to run a compile-time weaver to modify the bytecode so that the continuation-style pausing is available. At runtime, pausing is used to schedule a large number of actors over a small number of kernel threads.

In all, this is a really interesting set of features that adapts the Erlang/Scala actor model into the statically typed world of Java pretty nicely.

From a usage point of view, it’s kind of a pain. Compile-time weaving sucks rocks as it puts a kink in every tool chain plus there’s nothing happening that couldn’t be done at runtime with a Java agent, as far as I can tell. I also had a lot of time to get the Weaver to run on my first stab at the code. It was far from obvious that the error messages were indicating I had forgotten @pausable on the non-Actor classes. Fortunately, I know enough about ASM to tell what I was missing. And once I got it running I got some weird NoSuchMethodErrors due to incorrectly specifying @pausable on methods that didn’t need it.

These bumps didn’t bother me too much though – this is a new project and I understand that it’s early for that kind of help.

Now to a little code. This is really pretty much a port from the Scala code into Java, which was pretty close. I broke the code (previously in one file) into Ring (the main code), Message, TokenMessage, NodeActor, and TimerActor. You’ll notice there is a LOT more code with the Java version than the Scala or Erlang versions.

import java.util.ArrayList;
import java.util.List;

import kilim.Mailbox;
import kilim.pausable;

@pausable
public class Ring {

public static void main(String arg[]) {
new Ring().startRing(Integer.parseInt(arg[0]));
}

public void startRing(int n) {
List<NodeActor> nodes = spawnNodes(n, startTimer());
Mailbox<Message> mailbox = connectNodes(n, nodes);
mailbox.putnb(Message.START);

try { Thread.sleep(100000000); } catch(InterruptedException e) {}
}

private TimerActor startTimer() {
TimerActor actor = new TimerActor(new Mailbox<Message>());
actor.start();
return actor;
}

private List spawnNodes(int n, TimerActor timer) {
System.out.println(“constructing nodes”);
long startConstructing = System.currentTimeMillis();

List<NodeActor> nodes = new ArrayList<NodeActor>(n+1);
for(int i=0; i<n; i++) {
nodes.add(new NodeActor(i, new Mailbox<Message>(), timer.getInbox()));
nodes.get(i).start();
}

long endConstructing = System.currentTimeMillis();
System.out.println(“Took ” + (endConstructing-startConstructing) + ” ms to construct ” + n + ” nodes”);
return nodes;
}

private Mailbox<Message> connectNodes(int n, List<NodeActor> nodes) {
System.out.println(“connecting nodes”);
nodes.add(nodes.get(0));
for(int i=0; i<n; i++) {
nodes.get(i).connect(nodes.get(i+1).getInbox());
}
return nodes.get(0).getInbox();
}
}

It’s very important that the Ring class is marked @pausable or the weaver won’t work. Now the message classes. I defined some immutable singleton messages in Message and a mutable TokenMessage:

public class Message {
public static final Message START = new Message();
public static final Message STOP = new Message();
public static final Message CANCEL = new Message();
}

public class TokenMessage extends Message {
public final int source;
public int value;

public TokenMessage(int source, int value) {
this.source = source;
this.value = value;
}
}

And here’s the node actor translated into a Kilim Task (note the @pausable annotation on the execute() method, which is defined in Task):

import kilim.Mailbox;
import kilim.Task;
import kilim.pausable;

public class NodeActor extends Task {
private final int nodeId;
private final Mailbox<Message> inbox;
private final Mailbox<Message> timerInbox;
private Mailbox<Message> nextInbox;

public NodeActor(int nodeId, Mailbox<Message> inbox, Mailbox<Message> timerInbox) {
this.nodeId = nodeId;
this.inbox = inbox;
this.timerInbox = timerInbox;
}

public Mailbox<Message> getInbox() {
return this.inbox;
}

public void connect(Mailbox<Message> nextInbox) {
this.nextInbox = nextInbox;
}

@pausable
public void execute() throws Exception {
while(true) {
Message message = inbox.get();
if(message.equals(Message.START)) {
System.out.println(System.currentTimeMillis() + ” ” + nodeId + “: Starting messages”);
timerInbox.putnb(Message.START);
nextInbox.putnb(new TokenMessage(nodeId, 0));
} else if(message.equals(Message.STOP)) {
//System.out.println(System.currentTimeMillis() + ” ” + nodeId + “: Stopping”);
nextInbox.putnb(Message.STOP);
break;
} else if(message instanceof TokenMessage) {
TokenMessage token = (TokenMessage)message;
if(token.source == nodeId) {
int nextVal = token.value+1;
if(nextVal % 10000 == 0) {
System.out.println(System.currentTimeMillis() + ” ” + nodeId + “: Around ring ” + nextVal + ” times”);
}

if(nextVal == 1000000) {
timerInbox.putnb(Message.STOP);
timerInbox.putnb(Message.CANCEL);
nextInbox.putnb(Message.STOP);
break;
} else {
token.value = nextVal;
nextInbox.putnb(token);
}
} else {
nextInbox.putnb(token);
}
}
}
}
}

And for completeness, here’s the Timer Actor:

import kilim.Mailbox;
import kilim.Task;
import kilim.pausable;

public class TimerActor extends Task {
private final Mailbox<Message> inbox;
private boolean timing;
private long startTime;

public TimerActor(Mailbox<Message> inbox) {
this.inbox = inbox;
}

public Mailbox<Message> getInbox() {
return this.inbox;
}

@pausable
public void execute() throws Exception {
while(true) {
Message message = inbox.get();
if(message.equals(Message.START) && !timing) {
startTime = System.currentTimeMillis();
timing = true;
} else if(message.equals(Message.STOP) && timing) {
long endTime = System.currentTimeMillis();
System.out.println(“Start=” + startTime + ” Stop=” + endTime + ” Elapsed=” + (endTime-startTime));
timing = false;
} else if(message.equals(Message.CANCEL)) {
break;
}
}
}
}

To compile and weave, you’ll do something like this:

$ export KCP=$KILIM/libs/asm-all-2.2.3.jar:$KILIM/classes
$ javac -cp $KCP -d bin src/*.java
$ java -cp $KCP kilim.tools.Weaver -d ./bin ./bin
$ java -cp $KCPL./bin Ring 100

And finally the results, here in comparison with Erlang and Scala 2.7.3/JDK 1.6:

Language Spawn 100 Send 100M messages Spawn 20k
Erlang R12B 0.2 ms 77354 ms 120 ms
Scala 2.7.3 10 ms 121712 ms 315 ms
Kilim 3 ms 78390 ms 192 ms

So, Kilim demonstrates process creation faster than Scala (but still slower than Erlang) and message-passing in the realm of Erlang and significantly faster than Scala. That’s pretty darn impressive! I’m looking forward to watching where this library goes.

Comments

16 Responses to “Java Actors with Kilim”
  1. Tom says:

    Thanks for the detailed coverage here. It shows that Kilim has some real meat, though I’d still love to see light-weight processes at the MVM level.

  2. Fabien says:

    I was intrigued by your test, I tried it in Java. At first with a bad algorithm, then with a better one. In my results plain Java is not more complicated and much faster than Scala (and even Erlang)!

    See http://chasethedevil.blogspot.com/2009/01/running-rings-around-plain-java-killer.html

  3. Alex says:

    Thanks Fabien. I think this is already well-covered in the comments on your blog but the point behind actors is not to make our programs faster but to make it easier to write safe concurrent programs that can continue to work and take advantage of all cores as the # of cores increases.

  4. Kyle Cordes says:

    > Compile-time weaving

    Meh. Real Programmers munge the code at classload time. :-)

  5. Anonymous says:

    I forward you a comment from D Andreou on my blog, that I believe provides an accurate explanation why the test is completely meaningless:
    “It’s obvious why a single thread is the fastest. There is no concurrency in this program. At most, only a single thread can work. If this test is about concurrency, it’s a failed one, and perhaps you should modify your conclusions to reflect that (and not prompt others naively using a single thread where real concurrency exists).

    If you want to make the test more interesting, an easy way is to put more tokens there, say at least 4. The single thread won’t be able to cope with that.”

    Unfortunately creating more than 1 token (e.g. 2) at each round makes the increase of messages exponential (a max of 36 is almost equivalent in time of handling 10M messages), and I suppose one would need other changes to really make sense of it.

    As it is the test is a single threaded problem, you can not benchmark multithreading libraries based on that test.

  6. Alex says:

    Yep, that’s why I said it wasn’t worth much it. For me, it was about showing that a) spawning processes is fast, b) you can spawn a lot of processes and c) sending messages is fast.

  7. @Alex And such tests also say about the speed of context switching among actors. Based on the experiments we did, (the results of which are available at http://osl.cs.uiuc.edu/~rkumar8/ActorFoundry.ppt) showed that Scala’s actor library loses out to Kilim in such benchmarks because the context switching in Scala is slower (Kilim’s weaver is they key). That means that the grain of actors in Scala needs to be coarser to obtain good cost-benefit ratio.

    For scalable multicore programs, we believe in “as fine as possible” grain for actors.

    Concurrent performance requires among other things a smart run-time and specifically a good scheduler.

  8. I just saw this post on Javalobby and posted my reply there, but I figured I’ll repeat myself here.
    —-

    This is sriram, Kilim’s author. Thanks for the writeup. Just wanted to point out a slight error in the benchmark: you must allow the JIT to warm up.

    Here’s the difference in creation times (on a 2.4GHz Mac Pro)

    Warming up JIT
    constructing nodes
    Took 225 ms to construct 20000 nodes

    JIT warmed up
    constructing nodes
    Took 51 ms to construct 20000 nodes

  9. @Alex – Would you mind posting or emailing me the source for the Scala version? I’d like to test it against a tweaked version and I’m too lazy to write the handful of lines of code to duplicate the test.

  10. Alex,

    Any update to this? You mention kilim being a library to watch – and looking at your code you are likely on v0.6 (@pausable annotation) rather than v0.7.2 (Pausable exception specification).

    I would love to read an update post from you, especially if you have had any closer look at actor/messaging libraries.

    Cheers,
    –binkley

  11. Alex Miller says:

    I can’t say I’d use a Java library for actors these days. I’d much prefer to use Scala actors (or Scala with Akka), or Groovy and Gpars, or heck even Erlang. If I were going to use actors in Java, I’d probably try Actorom. Really, I don’t need the hassle of dealing with a compilation-time bytecode weaver.

  12. Sakshi says:

    Sir,
    I am new bee to Thread Programming. Kindly enlighten me on the fact that who calls execute() method defined in the subclasses of Task.java in Kilim.
    Sakshi.

  13. Alex Miller says:

    @Sakshi: The NodeActor is a subclass of Task which is a subclass of java.lang.Thread. When start() is called on the Thread, the run() method will be called by the JVM. The run() method is defined in Task and will call the execute() method. This is a classic example of the Template design pattern.

  14. Sakshi Rastogi says:

    Dear All,
    I want to add a new function to terminate the running task and replace it with another task that should run on the same thread that was being used by the task to be terminated. That is the following functions have to be added::

    public Thread Terminate(){
    this.done=true;
    Thread th=Thread.currentThread();
    return th;
    }

    public void Replace(Task t,Thread w){

    //Assigns task the thread w and runs it.

    }

    Kindly guide me regarding the second method and correct me if I am wrong in writing the first method.
    I am writing these methods in Task.java. They will be called from the subclass of Task.java.

    [P.S.: I have understood the execute() method mystery I had put forward in the previous post. Thanks to Alex Sir.]

  15. Sakshi Rastogi says:

    Dear All,
    I wrote the following function in Task.java.

    public Thread Terminate(){
    Thread w=this.currentThread;
    System.out.println(“Terminate!”);
    this.done=true;
    return w;
    }
    public void Replace(Thread w){
    this.pinToThread();
    WorkerThread wt=new WorkerThread(this.scheduler);
    this.preferredResumeThread=wt;
    }

    I call them from MyTask.java::

    package kilim.examples;

    import kilim.Mailbox;
    import kilim.Pausable;
    import kilim.Task;

    public class MyTask extends Task {
    static Mailbox mb = new Mailbox();

    public static void main(String[] args) throws Exception {
    Task t = new MyTask().start();
    Thread.sleep(10);
    System.out.println(t.id());
    mb.putnb(“First_Task\n”);
    mb.putnb(“Sakshi_Here\n”);
    Thread w=t.Terminate();
    Task tt=new MyTask();
    tt.Replace(w);
    tt.start();
    System.out.println(tt.id());
    mb.putnb(“Second_Task\n”);
    mb.putnb(“Sakshi_again\n”);
    mb.putnb(“done”);
    System.exit(0);
    }

    public void execute() throws Pausable {

    while (true) {

    String s = mb.get();

    if (s.equals(“done”))
    break;
    System.out.print(s);
    }
    System.exit(0);
    }
    }

    Though it seems to work yet..I am not sure of its correctness. Kindly guide me.

  16. Anonymous says:

    Why care if Kilim is successful Java framework or not, it has anyway taught us a new way of doing things in life. Come up with a Java framework, release it as open source and take credit from Java community. On the other hand, at the back force fit some E=mc2 theory to it and present it to University for getting a PhD degree, thereby killing two birds in one stone. Extremely smart, way to go!

    Universities anyway get free dollars from Uncle Sam, so they can afford to be Santa Claus to award PhDs to Java frameworks or perhaps to even product manuals!

    Research is no more about being selfless and working for benefit of mankind. It is about outsmarting the system to get a degree!

    “I never perfected an invention that I did not think about in terms of the service it might give others… I find out what the world needs, then I proceed to invent….” – Thomas Alva Edison

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!