Is Go a Systems Programming Language?
With automatic garbage collection, is Go really suitable as a systems programming language?
A topic that arises from time to time is whether Go is a systems programming language. The creators of Go have characterized it as such but many object to that label.
With automatic garbage collection, it does not seem to fit the bill. You would not, for example, want to write an operating system kernel with a language using a garbage collector.
However, it helps to clarify by looking at Wikipedia’s definition of systems programming:
Systems programming, or system programming, is the activity of programming computer system software.
The primary distinguishing characteristic of systems programming when compared to application programming is that application programming aims to produce software which provides services to the user directly (e.g. word processor),
whereas systems programming aims to produce software and software platforms which provide services to other software, are performance constrained, or both (e.g. operating systems, computational science applications, game engines, industrial automation, and software as a service applications).
In this regard, I think it is indeed fair to label Go a systems programming language. We can also see this reflected in the types of software built using Go:
- Docker — A container technology. Apps
Bcan run on the same OS in different containers. One container can make it look as if
Ais running on Red Hat Linux while the container for
Bmakes it look as if it is running on Ubuntu Linux.
- Kubernetes — A system for managing multiple containers across multiple computers.
- InfluxDB — A database system for storing time series data to be analyzed in realtime.
- Traefik — Is used for load balancing. Meaning it takes network requests from clients and try to route it to a server which has available capacity.
- CockroachDB — Distributed database.
- Geth — Implementation of an Ethereum crypto currency node. Can parse and verify the blockchain and its smart contracts.
If you look at this stuff, you see that this is not typical end user stuff. It is not Photoshop, MS Word or Angry Birds. It is not stuff that normal end users interact with directly. Rather, it is services used by end user software.
The performance of this software is often critical. How many requests can be handled, amount of memory used, latency, and so on are important metrics. In these circumstances, you want programming languages that make it easy to utilize hardware resources in an optimal way. These resources include things such as:
- Microprocessor cores.
Go is designed specifically to give developers fine control over these resources. It has a built in concurrency system based on so-called goroutines, which are a hybrid between coroutines and hardware threads. This allows workloads to be easily be distributed over multiple CPU cores.
While Go does not allow the same fine-tuned control of memory usage as given by C/C++, it gives much stronger control than managed languages such as Java, C#, Python, etc. You can define memory layout for objects in Go and use real pointers to point to subsets of those objects. This allows you, for example, to create custom memory allocators on top of the garbage collection system or keep allocations to a minimum.
Thus you have better ability than, say, Java in limiting your use of a limited resources such as memory. You are also able to arrange blocks of memory in a fashion more optimal for cache usage.
These are things that matter for systems programming. For application programming, it is not as well-suited. Take languages such as Objective-C or Swift. They allow you to easily define a GUI and store in a file. Later the file can be loaded and button actions connected to methods in your code.
One thing I often associate with system software is real time systems. In OS kernels, microprocessors, robots, etc., you need very low latency to respond to the real world. Typically, garbage-collected languages are poorly suited for this as they often periodically freeze to collect unused memory. Imagine a rocket such as the Falcon 9 throttling its engines while trying to land, and then suddenly a garbage collector kicks in and causes updating of the thrust direction and angle to briefly stop. The rocket would likely crash into the ground.
Here is an interesting difference between Java and Go. Java comes with a variety of garbage collectors that can be tuned and tweaked in all sorts of ways. Usually emphasis is not on low latency but maximum throughput.
While this is still something I am trying to understand better, the Go team has gone entirely the opposite direction. They have built a garbage collector without much that you can tweak and which is heavily optimized towards low latency. As far as I understand, a normal Java garbage collector stops execution for about 50 milliseconds on average while a Go garbage collector only does that for 0.5 milliseconds.
Now it could be argued that Java chooses performance instead, but whatever advantages their garbage collector gives, it doesn’t seem to follow in various performance tests:
- Go vs Java benchmarks: this is a collection of some well-defined smaller programs used in performance tests.
- Simple performance test by Sunny Radadiya.
In the past, I have interacted on Twitter with people working for NASA. They have claimed to use Go for collecting data from measuring instruments. Since measurements happen in real time, you cannot have stop-the-world garbage collection.
Over the years, I have kept hearing stories like that about Go, but you seldom hear anything like that about Java or C#. Thus Go does seem to fit into a space between C/C++ and Java/C#. Sunny characterized it this way:
Go lang and Java are not supposed to serve the same type of tasks — Java is enterprise development language whereas Go is a system programming language.