Message Passing in Elixir, Clojure and Go

I have spent some time recently learning Elixir. I have always been interested in Erlang, but the syntax has put me off in the past. One of the examples in the book Programming Elixir involves creating 1,000,000 processes and sending and incrementing an integer through the chain. The Elixir code is:

# Excerpted from "Programming Elixir",
# published by The Pragmatic Bookshelf.
defmodule Chain do
  def counter(next_pid) do    
    receive do
      n ->
        send next_pid, n + 1
    end
  end

  def create_processes(n) do
    last = Enum.reduce 1..n, self,
             fn (_,send_to) ->
               spawn(Chain, :counter, [send_to])
             end

    # start the count by sending
    send last, 0

    # and wait for the result to come back to us
    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end

This run in about 8-9sec on my Macbook Pro for 1,000,000 processes.

I was interested in how this would work in Clojure using core.async. I came up with the following code that I think closely follows the intent of the Elixir code.

(ns scratch.core
(:require [clojure.core.async :as async :refer [go <! chan >!! <!! >!]])
(:gen-class))

(defn inc-and-pass-on [in out]
  (go (let [n (<! in)]
      (>! out (inc n)))))

(defn run-it [n]
  (let [chans (partition 2 1 (repeatedly n chan))
        start (first (first chans))
        end (last (last chans))]
    (doseq [c chans]
      (let [[in out] c]
        (inc-and-pass-on in out)))
    (>!! start 1)
    (println "result is " (<!! end))))

(defn run []
    (let [start (. System currentTimeMillis)
          _     (run-it 1000000)
          end   (. System currentTimeMillis)]
    (println "Ran in" (- end start) "ms")))

(defn -main [& args]
  (run))

I was very impressed that this took about the same time as the Elixir code, after a few runs to allow the Hotspot optimisations to kick in. The non-blocking nature of Clojure’s core.async go blocks are impressive. This also seemed to take about the same amount of memory as the Elixir version.

Lastly I thought I would have a try at doing this in Go, as core.async follows a similar CSP model as Go. I’m not very familar with Go, but quickly came up with the following:

package main

import "fmt"
import "time"

func inc(in chan int, out chan int) {
  var i int
  i = <- in
  out <- i + 1
}

func main() {
  start := time.Now()
  const n = 1000000
  var chans [n]chan int
  for i := 0; i < n; i++ {
    chans[i] = make(chan int)
  }

  for i:= 1; i < n; i++ {
    go inc(chans[i-1], chans[i])
  }

  chans[0] <- 1
  result := <- chans[n-1]
  end := time.Now()
  fmt.Println("Result is ", result, " and it took ", end.Sub(start))
}

Using Go 1.4 the compiled binary executes this in about 2.3sec. I am very surprised with this, how fast Go is compared with the others. It seems to use about the same memory too as the Elixir version. About 2 Gig. If only I prefer the more functional style of Elixir/Clojure. Go still allows shared mutable state issues without care, though worth investigating a bit more given these results.

Disclaimer: Take micro benchmarks with a grain of salt.

Written on August 14, 2015