Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Why We Switched From Python to Go, Part 2

DZone's Guide to

Why We Switched From Python to Go, Part 2

We look at performance issues surrounding Python and Go, why Go looks to be the better language for writing performant code, and throw in a look at Elixir just for fun.

Free Resource

Sensu is an open source monitoring event pipeline. Try it today.

Disadvantages of Using Golang

Disadvantage 1 - Lack of Frameworks

Go doesn't have a single dominant framework like Rails for Ruby, Django for Python or Laravel for PHP. This is a topic of heated debate within the Go community, as many people advocate that you shouldn't use a framework to begin with. I totally agree that this is true for some use cases. However, if someone wants to build a simple CRUD API they will have a much easier time with Django/DJRF, Rails Laravel or Phoenix.

Update: as the comments pointed out there are several projects that provide a framework for Go. RevelIrisEchoMacaron, and Buffalo seem to be the leading contenders. For Stream's use case, we prefer to not use a framework. However for many new projects that are looking to provide a simple CRUD API the lack of a dominant framework will be a serious disadvantage.

Disadvantage 2 - Error Handling

Go handles errors by simply returning an error from a function and expecting your calling code to handle the error (or to return it up the calling stack). While this approach works, it's easy to lose scope of what went wrong to ensure you can provide a meaningful error to your users. The errors package solves this problem by allowing you to add context and a stack trace to your errors.

Another issue is that it's easy to forget to handle an error by accident. Static analysis tools like errcheck and megacheck are handy to avoid making these mistakes.

While these workarounds work well it doesn't feel quite right. You'd expect proper error handling to be supported by the language.

Disadvantage 3 - Package Management

Go's package management is by no means perfect. By default, it doesn't have a way to specify a specific version of a dependency and there's no way to create reproducible builds. Python, Node, and Ruby all have better systems for package management. However, with the right tools, Go's package management works quite well.

You can use Dep to manage your dependencies to allow specifying and pinning versions. Apart from that, we've contributed an open-source tool called VirtualGo which makes it easier to work on multiple projects written in Go.

Python vs Go

One interesting experiment we conducted was taking our ranked feed functionality in Python and rewriting it in Go. Have a look at this example of a ranking method:

{
"functions": {
"simple_gauss": {
"base": "decay_gauss",
"scale": "5d",
"offset": "1d",
"decay": "0.3"
},
"popularity_gauss": {
"base": "decay_gauss",
"scale": "100",
"offset": "5",
"decay": "0.5"
}
},
"defaults": {
"popularity": 1
},
"score": "simple_gauss(time)*popularity"
}

Both the Python and Go code need to do the following to support this ranking method:

  1. Parse the expression for the score. In this case, we want to turn this string "simple_gauss(time)*popularity" into a function that takes an activity as input and returns a score as output.
  2. Create partial functions based on the JSON config. For example, we want "simple_gauss" to call "decay_gauss" with a scale of 5 days, offset of 1 day, and a decay factor of 0.3.
  3. Parse the "defaults" configuration so you have a fallback if a certain field is not defined on an activity.
  4. Use the function from step 1 to score all activities in the feed.

Developing the Python version of the ranking code took roughly 3 days. That includes writing the code, unit tests, and documentation. Next, we've spent approximately 2 weeks optimizing the code. One of the optimizations was translating the score expression (simple_gauss(time)*popularity) into an abstract syntax tree. We also implemented caching logic which pre-computed the score for certain times in the future.

In contrast, developing the Go version of this code took roughly 4 days. The performance didn't require any further optimization. So while the initial bit of development was faster in Python, the Go-based version ultimately required substantially less work from our team. As an added benefit, the Go code performed roughly 40 times faster than our highly-optimized Python code.

Now, this is just a single example of the performance gains we've experienced by switching to Go. It is, of course, comparing apples to oranges:

  • The ranking code was my first project in Go.
  • The Go code was built after the Python code, so the use case was better understood.
  • The Go library for expression parsing was of exceptional quality.

Your mileage will vary. Some other components of our system took substantially more time to build in Go compared to Python. As a general trend, we see that developing Go code takes slightly more effort. However, we spend much less time optimizing the code for performance.

Elixir vs Go - The Runner Up

Another language we evaluated is Elixir. Elixir is built on top of the Erlang virtual machine. It's a fascinating language and we considered it since one of our team members has a ton of experience with Erlang.

For our use cases, we noticed that Go's raw performance is much better. Both Go and Elixir will do a great job serving thousands of concurrent requests. However, if you look at individual request performance, Go is substantially faster for our use case. Another reason why we chose Go over Elixir was the ecosystem. For the components we required, Go had more mature libraries whereas, in many cases, the Elixir libraries weren't ready for production usage. It's also harder to train/find developers to work with Elixir.

These reasons tipped the balance in favor of Go. The Phoenix framework for Elixir looks awesome though and is definitely worth a look.

Conclusion

Go is a very performant language with great support for concurrency. It is almost as fast as languages like C++ and Java. While it does take a bit more time to build things using Go compared to Python or Ruby, you'll save a ton of time spent on optimizing the code.

Go's combination of a great ecosystemeasy onboarding for new developers, fast performancesolid support for concurrency, and a productive programming environment makes it a great choice.

We won't be saying goodbye to Python anytime soon, but going forward all performance-intensive code will be written in Go.

Sensu: workflow automation for monitoring. Learn more—download the whitepaper.

Topics:
python ,go ,performance

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}