Brians Brain — Errata & Echoes
Brians Brain has been served in two versions on my blog, the traditional heavy functional variant and the hip blazing transient. These have both been scrutinized, torn apart and rebuilt, copied and commented on across the internet. In this post I’ll comment on a few of the discussions.
Errata
The first post on Brians Brain was an attempt to show a way of writing code which mimicked the way you would explain the simulation to your uncle. The second installment was purely based on getting performance out of the simulation, cutting corners here and there. This turned out to be a very bad idea.
I mention in the beginning that I’ll be using a single dimensional vector for the board representation, translating to coordinates when rendering. That won’t be entirely accurate of course (since it’s impossible), but it’ll come so close that it doesn’t really show. That enabled me to write the neighbor-lookup as simply subtracting the differences in index:
(defn torus-coordinate
[idx]
(cond (neg? idx) (+ idx board-size)
(>= idx board-size) (- idx board-size)
:else idx))
(def neighbors [-1 1 (dec above) above (inc above)
(dec below) below (inc below)])
(defn on-neighbors
[idx]
(count
(filter #(= :on (nth board %))
(map #(torus-coordinate (+ idx %)) neighbors)
And this is not 100% accurate because of the way I wrap the world, but I reasoned within myself that since the purpose of the post was to show off transients, that this didn’t matter — And as jdz mentioned such an oversight goes against my own principles. Oh but if only that was it.…
It turns out that this way of checking neighbors is blazingly fast — so fast in fact, that it’s almost the only huge gain in the computational model! That’s really bad, because it means that the way I was using transients wasn’t buying me any performance. Transients is still a viable way to optimize, just not the way I did it. And the root cause is actually that I didn’t pinpoint “on-neighbors” to be the slow kid in the class.
My appologies to all who read that post!

I should also mention, that I actually tried to tap directly into Javas Arrays following the great performance boosting tips found: here. But it still gave me about 200 msecs on an 80x80 board. I then tried to reduce it to a single-dimensional array containing coordinate data, still about the same speed. I then tried a single dimensional transient vector, still the same speed. I tried an optimized loop (instead of filter) which bails as soon as it can, still same speed. All in all, both I and the people I’ve consulted with have no idea why Clojure is taking so long for this simple operation — I might get back to that once I find out though.
The only reason I haven’t removed the post from my site, is the fact that all the graphics optimizations are still valid and useful.
Echoes
Several people made some attempts are replicating the simulation in various corners of the internet and one guy really blew me away — RARELY do I get blown away by code.
Haskell
Will Donnelly really impressed me. The main logic is only about 6 lines of beautiful Haskell
getPeers world (x,y) = (world ! (x,y), length . filter (== On) $ neighbors)
where neighbors = [getCell x y | x <- [x-1 .. x+1], y <- [y-1 .. y+1]]
getCell x y = world ! (clip worldX x, clip worldY y)
clip max val | val < 1 = clip max $ val + max - 1
| val > max = clip max $ val - max + 1
| otherwise = val
His solution is purely functional (its Haskell), it performs outstandingly and it runs in parallel. With latest Haskell compiler you get ~170% CPU utilization — Using version 6.8 (standard Ubuntu repo) you’ll only get ~100% because the cores take turns on running for some reason.
Python
This guy calls himself ‘ambient’ and he starts his post out like this:
Lau Jensen posted about Clojure implementation of Brian’s Brain.
Later, he posted about how Clojure is better than Python.
Well, here’s my Python version of Brian’s Brain that’s a lot faster and more compact, although irrevocably obfuscated.
Firstly, he missed the point entirely. Saying that your 100% imperative mutable version runs faster than a 100% functional version, is similar to saying “I built my bridge using nothing but paper clips in the time it took you to mix the cement”. Anyway — His code is almost unreadable to me, but I’ll give him this: It performs very well! So well in fact, that I think I’ll have to review Python-performance one more time.
Common Lisp
jdz is preparing an article, which I’ve read a preview of. He takes my code and modifies it, bringing up some very good style points, improving on certain areas. And then next up I think he plans to implement it in Common Lisp. Keep an eye out, it will definitely be worth reading!
Conclusion
I skipped corners — I learned my lesson — I won’t do it again. And as always, I’m very grateful for the people who pick up my posts/code and mix and mash it into new an interesting pieces — I learn something every time!
/Lau
| | |
Comments are closed.

about 4 months ago
Lau, I think the back-and-forth of this kind of discussion is useful to everyone. It is enlightening to hear from Haskell, Python and other language practitioners about the strengths and weaknesses of Clojure and most importantly these conversations serve as a reality check for everyone.
I hope that when you write “I won’t do it again” you mean “I will do it again” ;)
By the way, I like the new theme…
about 4 months ago
@Antoni: Thank you very much for your kind words of encouragement. I must disappoint you however — I really did mean i will not do it again. I don’t mind sticking my hands in the kettle when it’s hot, but I’m committed to delivering quality code — I only temporarily forget that commitment in order to deliver a blogpost in a hurry :)
Glad you like the theme — Disclojure.org’s is also nice, clean and cool :)
/Lau