Skip to content

Instantly share code, notes, and snippets.

@adityagodbole
Last active August 29, 2015 14:15
Show Gist options
  • Save adityagodbole/fb14fef197430100ca01 to your computer and use it in GitHub Desktop.
Save adityagodbole/fb14fef197430100ca01 to your computer and use it in GitHub Desktop.

Over the last weekend I fiddled around a bit with the Go Programming Language. I had sort of written a few snippets here and there before this, while I was evaluating it and learning the basics. This was the first time I wrote more than a few hundred lines of code in Go. One of the unique features of Go is extremely lightweight concurrency primitives called goroutines (a play on coroutines). It also has first class communication mechanisms called channels which you can use to send data between these goroutines. These channels also double up as synchronisation mechanisms. By having goroutines and channels, go lends itself towards a “service oriented” design. You can model your entire process in terms of producers and consumers (and consumers+producers) and set it up to run concurrently. As opposed to object oriented design (static blobs of data manipulated by privileged sections of the code) or functional design (pure data being acted upon by pure functions resulting in a new state of the universe), you can model your entities as miniature “servers”, waiting for the next request and performing some small action. So each “object” or “struct” from traditional languages can become a living entity in go, with it’s own concurrent lifecycle. The word concurrent is important here, thats what makes go unique.

So I decided to implement a library or package using which, a regular Go struct can be converted into one of these living “agents”. It was there that the lack of generics hit me hard. For a while I could not figure out how to do what I wanted, because of the lack of generics. Whereas in something like Java or C++ I could have quite quickly created somethine like Agent<T> where T could be any type or struct, there was no such facility in Golang. In cases like this, interfaces are just not enough.

However, Go has reflection and a pretty decent reflection package. But the question really was, how much of an overhead would a library like this introduce? So I wrote two versions - one which was only as generic as interfaces would allow me and the second one which went all the way using reflection.

The implementations are here. The non generic one is quite restrictive and not really what I would like to use as a user of the library. As can be seen, there isn't much elegance in the implementation of the generic version. Also note that in the generic version, errors like signature mismatch in calling the functions are thrown at runtime, quite like a dynamically typed language. I did add some memoisation where I could, to improve the runtimes of the generic version.

To test out the implementation, I create a given number of goroutines which simultaneously call the same function (in this case, it is a simple increment) on an agent. Once all are finished, the program prints the value and exits.

Here are the runtimes with a million goroutines calling the Inc method.

For the non generic version:

$ time ./atomicadd 1000000
1000010
real	0m4.703s
user	0m3.314s
sys	0m1.366s

And for the generic version:

time ./atomicadd_generic 1000000
1000010
real	0m5.312s
user	0m3.875s
sys 	0m1.429s

Using reflection a million times (and non-trivial reflection at that) adds an overhead of 13% to the overall runtime.

To summarise:

  • It is impossible to have type safe generics. You can use code generators, but they are not trivial to use to implement compile time generics.
  • Reflection can get the job done, but at the cost of type safety.
  • The cost of reflection is not too high - a cumulative overhead of 13% over 1 million calls. Again, the actual operation of incrementing an integer is not really representative of real world scarios. Your operations will me more complex and if they involve things like i/o or other system calls, the percentage overhead will be much lower.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment