- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Actor messaging throughput test
Mon, 2009-07-20, 20:16
As I found the Actor concept wonderful, I gave it a quick throughput test, hoping it is almost as efficient as a method call (because a message passing is conceptually like a method invocation). I ran the code (attached at the bottom) that forms a 8-node ring and circulates a token on it (10 million rounds total). It was tested on a 2.8 GHz 8-core Solaris box with more than enough memory. I used Scala 2.7.5. The throughput is not as good as I hoped - although 14 usec per hop isn't too bad, I hoped it would be better (particularly with loop-react).
[while-receive results]
1083616.107189 ms total, 0.10836161071889999 ms per round, 0.013545201339862499 ms per hop, 73826.88340387204 hops per second.
If while-receive is replaced with loop-react, the throughput becomes about halved. One thing I noticed is that there were 16 active threads running for the 8-node ring, which seems to indicate each actor is comprised of two threads (in the while-receive case, I saw 8 active threads, which makes sense). Can anyone explain why 16 threads had to be actively used with loop-react, which seems counter-intuitive given what react is supposed to be?
[loop-react results]
2251426.823 ms total, 0.22514268229999998 ms per round, 0.028142835287499997 ms per hop, 35533.02251831616 hops per second.
My questions are
1. Are these results expected? Anyone please submit your results (on this code or similar)?
2. Can I improve my code for better performance?
3. Why were 16 threads running actively with loop-react?
I'll note that I tested with ringSize set to 4 as well, which didn't change the throughput results much.
Any comments and insights welcome. Thank you.
Seung
object RingTest {
val ringSize = 8
val maxRounds = 10000000
case object Token
case object Exit
case class NextNode(passer: Actor)
val LOG = LogFactory.getLogger(getClass().getName())
class Node(id: Int) extends Actor {
var nextNode: Actor = null
override def act() {
var round = 0
val startTime = System.nanoTime
while (true) {
receive {
case Token =>
// Pass the token aggressively before doing anything.
assert(nextNode != null)
nextNode ! Token
round += 1
if (round % 10000 == 0) {
LOG.info("id=" + id + " round=" + round)
}
if (round == maxRounds) {
val endTime = System.nanoTime
printStats(startTime, endTime)
System.exit(0)
}
case NextNode(x) =>
nextNode = x
}
}
}
private def printStats(startTime: Long, endTime: Long) {
val timeTotal = (endTime - startTime) / 1000000.0
val timePerRound = timeTotal / maxRounds
val timePerHop = timePerRound / ringSize
LOG.info("" + timeTotal + " ms total, " +
timePerRound + " ms per round, " +
timePerHop + " ms per hop, " +
(1000 / timePerHop) + " hops per second.")
}
}
def logConfigs() {
LOG.config("ring size = " + ringSize)
LOG.config("max rounds = " + maxRounds)
}
def main(args: Array[String]) {
val nodes = new Array[Actor](ringSize)
for (i <- 0 until ringSize) {
nodes(i) = new Node(i)
nodes(i).start()
}
for (i <- 0 until ringSize) {
nodes(i) ! new NextNode(nodes((i + 1) % ringSize))
}
nodes(0) ! Token
LOG.info("Passed the token.")
}
}
[while-receive results]
1083616.107189 ms total, 0.10836161071889999 ms per round, 0.013545201339862499 ms per hop, 73826.88340387204 hops per second.
If while-receive is replaced with loop-react, the throughput becomes about halved. One thing I noticed is that there were 16 active threads running for the 8-node ring, which seems to indicate each actor is comprised of two threads (in the while-receive case, I saw 8 active threads, which makes sense). Can anyone explain why 16 threads had to be actively used with loop-react, which seems counter-intuitive given what react is supposed to be?
[loop-react results]
2251426.823 ms total, 0.22514268229999998 ms per round, 0.028142835287499997 ms per hop, 35533.02251831616 hops per second.
My questions are
1. Are these results expected? Anyone please submit your results (on this code or similar)?
2. Can I improve my code for better performance?
3. Why were 16 threads running actively with loop-react?
I'll note that I tested with ringSize set to 4 as well, which didn't change the throughput results much.
Any comments and insights welcome. Thank you.
Seung
object RingTest {
val ringSize = 8
val maxRounds = 10000000
case object Token
case object Exit
case class NextNode(passer: Actor)
val LOG = LogFactory.getLogger(getClass().getName())
class Node(id: Int) extends Actor {
var nextNode: Actor = null
override def act() {
var round = 0
val startTime = System.nanoTime
while (true) {
receive {
case Token =>
// Pass the token aggressively before doing anything.
assert(nextNode != null)
nextNode ! Token
round += 1
if (round % 10000 == 0) {
LOG.info("id=" + id + " round=" + round)
}
if (round == maxRounds) {
val endTime = System.nanoTime
printStats(startTime, endTime)
System.exit(0)
}
case NextNode(x) =>
nextNode = x
}
}
}
private def printStats(startTime: Long, endTime: Long) {
val timeTotal = (endTime - startTime) / 1000000.0
val timePerRound = timeTotal / maxRounds
val timePerHop = timePerRound / ringSize
LOG.info("" + timeTotal + " ms total, " +
timePerRound + " ms per round, " +
timePerHop + " ms per hop, " +
(1000 / timePerHop) + " hops per second.")
}
}
def logConfigs() {
LOG.config("ring size = " + ringSize)
LOG.config("max rounds = " + maxRounds)
}
def main(args: Array[String]) {
val nodes = new Array[Actor](ringSize)
for (i <- 0 until ringSize) {
nodes(i) = new Node(i)
nodes(i).start()
}
for (i <- 0 until ringSize) {
nodes(i) ! new NextNode(nodes((i + 1) % ringSize))
}
nodes(0) ! Token
LOG.info("Passed the token.")
}
}