During the month of December, I used the daily Advent of Code puzzles to teach myself Go.
My primary language for the past couple years has been Python, and I’m very happy with it. It’s a great “swiss army knife” language: it has a great package ecosystem and is relatively quick to program in (due to its simple syntax and dynamic type system). If there’s a task I want to do, Python can pretty much always do it: BeautifulSoup is great for web scraping, Flask for web services, Pandas/Numpy for data processing, matplotlib for data visualization, and the list goes on.
Furthermore, I’ve been becoming more interested in network programming and distributed systems. Python is “OK” in this domain, but it’s concurrency model (cough GIL cough) leaves a bit to be desired. Also, it turns out that static types have decent upsides.
So, I decided to push myself to write code in Go for this year’s AoC. It was a fairly rewarding experience.
Productivity wise, Go seems to reward more careful programming. Whereas Python permits ad-hoc function signatures, and you can use dicts and tuples as ad-hoc structs, Go prefers greater rigidity.
However, I didn’t find this as limiting as I thought I might. Formalizing struct layouts made my algorithms easier to read, and resulted in code that was more “self-documented” than you get in Python.
There was a “productivity” hit I took when writing puzzle solutions in Go, but it wasn’t as much as I’d feared. My usual workflow was to write my puzzle solutions in Python first, and then transcribe the solution in Go (rethinking as necessary to fit Golang idioms). This turned out to be fairly productive as I could use Python as a prototyping language, and then formalize my code in Go.
The Go standard library is less feature-rich than Python, but not to as extreme an extent as languages like C. Go provides basic string manipulation methods, a sane regex interface, and has first-class maps (dict equivalents) which I found to be fairly flexible.
Where Go really wow’d me was in the performance department. Using (nearly) identical algorithms, it was common for me to get a 5-10x performance improvement in my Go solutions versus my Python solutions.
I also wrote some Advent of Code solutions in C, for the puzzles that were particularly computationally intensive. Surprisingly, Go outperformed C consistently without C compiler optimizations, and only lagged behind C with compiler optimizations by a small margin, in most cases.
I did have a couple hiccups which I think are worth mentioning. In Python, dictionaries are (intentionally) very opaque. You can initialize one, and throw millions of key/value pairs in it without thinking. In Go, I found that
map cares a bit more about its size/capacity. In one puzzle, my Go solution ran much slower than I’d expected. After running a profiler, I found that
map operations were the culprit. After pre-allocating a large capacity to the map, my performance improved drastically.
// Without specifying initial capacity make(map[string]int) // With initial capacity make(map[string]int, 1000)
Go’s ecosystem is really exciting. I was drawn in by projects like etcd, boltdb, and btcd. Furthermore, it feels like there is a decent amount of excitement around lower-level languages like Go and Rust.
After “challenging” myself to use Go for a month, I came out of it with a quite positive opinion of Go. Now, I just need to find another “excuse” to use it. 😄
You can find my Go (and Python/C) solutions to Advent of Code 2017 here.