Running rings around plain Java
Alex Miller has a very interesting test of Actors. He finds out Scala performance is relatively low compared to Erlang, and Kilim is very near Erlang. But Kilim code is the most difficult to read in the lot.
I thought it would be simple to just do the same test in plain Java. I wrote the code for it duplicating the scala logic using Threads instead of Actors.
public class SimpleRing {
public static void main(String[] args) throws Exception {
SimpleRing ring = new SimpleRing();
RingNode node = ring.startRing(Integer.parseInt(args[0]));
node.sendMessage(new StartMessage());
}
public RingNode startRing(int n) {
RingNode[] nodes = spawnNodes(n, startTimer());
connectNodes(n, nodes);
return nodes[0];
}
private Timer startTimer() {
Timer timer = new Timer();
new Thread(timer).start();
return timer;
}
private RingNode[] spawnNodes(int n, final Timer timer) {
System.out.println("constructing nodes");
long start = System.currentTimeMillis();
RingNode[] nodes = new RingNode[n+1];
for (int i = 0; i < n ; i++) {
nodes[i] = new RingNode(i, timer, null);
new Thread(nodes[i]).start(); //later use pool
}
long end = System.currentTimeMillis();
System.out.println("Took "+(end-start)+"ms to construct "+n+" nodes");
return nodes;
}
private void connectNodes(int n, RingNode[] nodes) {
System.out.println("connecting nodes");
nodes[n] = nodes[0];
for (int i=0; i<n; i++) {
nodes[i].connect(nodes[i+1]);
}
}
interface Message {
String getType();
}
private static class StartMessage implements Message {
public String getType() {
return "START";
}
}
private static class StopMessage implements Message {
public String getType() {
return "STOP";
}
}
private static class CancelMessage implements Message {
public String getType() {
return "CANCEL";
}
}
private static class TokenMessage implements Message {
private int nodeId;
private int value;
public TokenMessage(int nodeId, int value) {
this.nodeId = nodeId;
this.value = value;
}
public String getType() {
return "TOKEN";
}
}
private static class RingNode implements Runnable {
private int nodeId;
private Timer timer;
private RingNode nextNode;
private BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
public RingNode(int id, Timer timer, RingNode nextNode) {
nodeId = id;
this.timer = timer;
this.nextNode = nextNode;
}
public void connect(RingNode node) {
nextNode = node;
}
public void sendMessage(Message m) {
queue.add(m);
}
public void run() {
while (true) {
try {
Message m = queue.take();
if (m instanceof StartMessage) {
log("Starting messages");
timer.sendMessage(m);
nextNode.sendMessage(new TokenMessage(nodeId, 0));
} else if (m instanceof StopMessage) {
log("Stopping");
nextNode.sendMessage(m);
break;
} else if (m instanceof TokenMessage) {
if (((TokenMessage)m).nodeId == nodeId) {
int nextValue = ((TokenMessage)m).value + 1;
if (nextValue % 10000 == 0) {
log("Around ring "+nextValue+" times");
}
if (nextValue == 1000000) {
timer.sendMessage(new StopMessage());
timer.sendMessage(new CancelMessage());
nextNode.sendMessage(new StopMessage());
break;
} else {
nextNode.sendMessage(new TokenMessage(nodeId, nextValue));
}
} else {
nextNode.sendMessage(m);
}
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
public void log(String s) {
System.out.println(System.currentTimeMillis()+" "+nodeId+": "+s);
}
}
private static class Timer implements Runnable {
private BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
private boolean timing = false;
private long startTime;
public void sendMessage(Message m) {
queue.add(m);
}
public void run() {
while (true) {
Message m;
try {
m = queue.take();
if (m instanceof StartMessage) {
startTime = System.currentTimeMillis();
timing = true;
} else if (m instanceof StopMessage) {
long end = System.currentTimeMillis();
System.out.println("Start="+startTime+" Stop="+end+" Elapsed="+(end-startTime));
timing = false;
} else if (m instanceof CancelMessage) {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
-
SimpleRing
- spawn 100: 11ms
- send 100M messages: 493073ms
-
Scala Actors
- spawn 100: 15 ms
- send 100M messages: 270104ms
UPDATE with slight changes plain Java rocks!