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.