Golang Generics Performance Evaluation and Implications

 

Generics is the latest upcoming feature in Golang. Introducing high level elegant syntax along with infinite possibilities. If you need an introduction kindly visit our post on Golang generics.

Unlike the previous post, This post is more concerned about evaluating the performance of Golang Generics and some future perspectives and implications. Any source code snippets used in this post can be found here. The rest of the post is organized as follows:

  1. Introduction to generics
  2. Benchmark experiments strategy
  3. Benchmark results
  4. Implications on Golang future

Introduction to generics

Generics has been around and used extensively (specially when writing libraries or frameworks) in many programming languages like c++, c#, java. However, generics are known to have some extra computational cost to explicitly typed code. With that in mind, it has been always a good idea to write in generics rather than explicit coding due to its huge advantages in readability, maintainability and code reuse.

The introduction of generics in Golang may raise many questions regarding its performance, backward compatibility, updating currently used standard libraries (which are build on interfaces and reflections) and whether it is worth it to use generics in Golang. To find the answers to this and more keep reading.

Designing the performance benchmark experiment 

 We will consider evaluating both time and memory costs in these two situations:

  1. light weight simple operations
  2. computationally demanding operations

And for each one of the previously aforementioned scenarios, we will compare the cost of using generics to the cost of using explicit typing and legacy interfaces.

Simple tasks

For a simple operation we might consider a task of adding two numbers. The next three code snippets will demonstrate the use of explicit type style, generic style and the legacy interface style respectively.

 

The Benchmarking code for these samples is demonstrated in the next three code snippets in the same order

The command to run those benchmarks is:

Computationally demanding tasks

For a computationally expensive task we have selected the famous Fibonacci” in its naïve implementation. As we did with the simple task evaluation, we are implementing three versions: an explicit style, a generic style and a legacy interface style. The code for these variations is listed below in a respective order along with their benchmark code.

Benchmark results

Testing environment :

We ran this test on a powerful Mac OS machine equipped with 24 core xeon processor along with 48GB Ram. Each test ran 100 times for accurate evaluation.

Simple task analysis:

As we may have noticed in the above figure, both the generic and explicit implementations had almost the same execution time and number of memory allocations which is rather impressive performance for a generic function compared to a an explicitly typed function. The legacy interface function came far behind them in terms of both memory and cpu time. The graph below compares the running time of these tasks in a bar plot graph.

 

Expensive task analysis:

The gap in the performance between the three techniques became more pronounced in a computationally extensive task as expected. The explicit code came a tad faster than the generic code, but not that far ahead, thus maintaining a relatively high performance and keeping simple elegant and readable code. The legacy implementation came worth as expected in both memory and time. The next bar plot graph illustrates this fact.
 
 

Which lead to the next section of this post, how will this affect the language, it’s usability and standard libraries?

Implications on Golang future

In this section we will discuss the impact of these changes and benchmarks on:
  1. currently written Golang standard libraries 
  2. currently running code in production servers
  3. code that will be written in the future

Currently written Golang standard libraries

Many of the current Golang standard libraries such as the famous “sort” were written using the legacy interface paradigm, furthermore they are used extensively in production code due to their famous reliability. So we can only wait to see if google will decide to update their libraries to take advantage of their new emerging generics technology. I’m guessing that the answer would be probably a big fat “FOR SURE” which leads us to the next bit.

Currently running code in production servers

If the previous assumption will evaluate to true, that means a lot of currently written packages that use Golang native libraries will break and will not be able to upgrade to the next version of go without considerable changes in their software artifacts. 

Code that will be written in the future

This is a happy time to consider starting using Golang with it’s new feature in your next project, taking advantage of all of its out of the box high performance tools and simple syntax. Happy coding.
 

Generics In Golang

Featured

Golang is an emerging technology that took the development world by storm. Introducing rapid software development that is consistent, strongly typed, insanely fast performance, and simple concurrency model; that is cheap, easy to learn and debug. 

As promised by google on twitter, finally generics are introduced in Golang.
 

This post will introduce the upcoming feature in Golang 1.18 called Generics. I’m going to assume basic familiarity with Golang. The rest of content is organized as follows:

  1. What are generics
  2. Why Golang?
  3. Golang before generics
  4. Generics in Golang
  5. Install Golang 1.18
  6. Simple stack using generics
  7. Implementation in Golang.

What are generics?

Generics are programming concepts that applies to functions, classes, and methods. That allows the usage and declaration of strongly typed software artifacts with complete independence on the type/s of their parameters. This can be achieved in a two step process.

First the function, method or class will provide a mechanism to accept the types as parameters.
Then the caller must provide those types when using the method. For example, if we need to provide a static function that receives two objects and returns the addition result.

Instead of writing a function for each type, we can make use of generics. As an illustration of how generics works in other languages, here are some examples in c++ and c#:

 

The above code snippets simply calls the add method with different data types and returns the addition result in the same data type as the arguments.

Why Golang?

If you haver ever used Golang (which I assume that you are) you may have came across some of its powerful syntax and programming paradigm. Golang natively included all the tools required to develop feature rich cross-platform applications. Including an impressive concurrency model, fault tolerance, amazingly realtime performance, unified database interface,…..

This completed with an outstanding community and google ownership leaves no stone unturned. Golang has unprecedented code reuse ability and package distribution. You can check the complete list of features here.

 

Golang before generics

Before the formal introduction to generics in Golang, creating a generic code was not impossible, but it involved the usage of type reflections, interfaces, and it was a bit frustrating to review and develop. This is what the  aforementioned add function would look like without the use of generics:

 

 
  • The idea here is use an empty interface to accept any kind of argument at line 9.
  • lines 10 throw 13, we check if both of the arguments are of the same type.
  • Lines 15 throw 31, checks the type of the arguments, casts the interface to that type and  perform the addition.
  • Lines 32 throw 34, returns error if the supplied type is not supported by the function.

Install Golang 1.18

In order to start using Golang generics, you must install Golang 1.18 or newer. Assuming that you have already an order version of Golang installed.
Just use this script.

 

If this works fine, you should see Golang 1.18 printed on the terminal window, If not please make a comment to get help.

 

Simple stack using generics

A very famous and useful application of generics is using stacks. If you don’t know about stacks, click here.

Generics can be a lot of help when it comes to the implementation of different data structures such as stacks, heaps, graphs, etc.. because it allows the development of a generic data structure rather than a typed locked ones.  We are going to implement the following methods:

  • pop: removes the first item of the stack
  • push: appends an item to the stack
  • peak: gets a peak at the next item in the stack

Implementation in Golang

The implementation of the stack itself would take place in an upcoming post. Here I would like to implement the add function (we have reviewed in c++ and c#) in Golang. And here it goes:

 
As we may have notice, there is some slight differences in Golang generics than other languages. For example: Golang used square brackets rather than the symbol “”.
Golang went further, it provided a mechanism (an optional mechanism i.e. not required) to restrict the type of data that goes into the function.
 
In the last code snippet the function “add” accepts two parameters a,b. The type of these parameters is denoted by the letter “T” where “T” can be any of these types. The function add also returns a variable of type “T”.
 
A detailed analysis and benchmarks of generics in Golang is to be expected in this blog. Stay tuned.