The Erlang Challenge

Joe Armstrong's SICS talk Concurrency Oriented Programming in Erlang contains a slide titled "Challenge 1" where he describes a kind of system benchmark. The challenge is to put N processes in a ring, send a simple message round the ring M times, then increase N until the system crashes. A simple implementation of his challenge could look like this:

ring_seed_beh(n) = \first.[
	CREATE next WITH ring_seed_beh(inc(n))
	BECOME ring_link_beh(next)
	SEND first TO first
]

ring_link_beh(next) = \first.[
	SEND first TO next
]

CREATE ring WITH ring_seed_beh(0)
SEND ring TO ring

This version starts with an initial seed process, and grows the ring by one process for each time a message traverses the ring. The "message" is just the identity of the first actor in the ring, providing the required circularity. Unfortunately, this program takes an extremely long time to create a significant number of processes. This is simply because the message circulating around the ring must pass through each of the increasing number of processes before arriving at the "end", adding a new process to the ring and starting all over again. Also, since process creation is intermixed with message passing, it is difficult to measure the benchmark questions Joe asks.

An alternate implementation allows direct specification of N processes and M messages. A similar seed-process is used to create the process ring. Once all the processes have been created, the initial message count is sent to the first actor. Each time a message passes through all processes in the ring, the message count is decremented. Once the message count reaches zero, message passing stops.

countdown_builder_beh(n) = \(first, m).[
	IF $n = 0 [
		BECOME countdown_ring_0_beh(first)
		SEND m TO first  # start message passing phase
	] ELSE [
		CREATE next WITH countdown_builder_beh(dec(n))
		BECOME countdown_ring_beh(next)
		SEND (first, m) TO next
	]
]

countdown_ring_0_beh(first) = \m.[
	IF $m = 0 [
		BECOME \_.[]
	] ELSE [
		SEND dec(m) TO first
	]
]

countdown_ring_beh(next) = \m.[
	SEND m TO next
]

CREATE countdown WITH countdown_builder_beh(123456)
SEND (countdown, 789) TO countdown

This implementation separates process-ring creation from the circulation of messages, allowing each phase to be timed individually. The process stops after circulating the required number of messages, allowing timing of the overall process. Increasing values of N processes can be tested, measuring the effects on system performance. An implementation of this benchmark was able to create over 100 million processes before experiencing performance degradation due to swapping. Message passing time was around 1µs and process creation time was approximately twice as long. Each actor/process occupies only 16 bytes of memory.