'. '

Go

From APIDesign

Jump to: navigation, search

Go is a programming language developed by Google. When it was introduced in 2009, it was promoted as:

After the usual initial hype it managed to keep some of its coolness. Go is actively used and supported by a huge company - e.g. it works and will continue to work for its use-cases quite well. Go got another boost with the rise of docker (as most of the docker ecosystem is written in Go).

Contents

Forget Go!

Go is mostly used in places where fast start, low system requirements and multi-threaded communication is needed and where lower level language like C is considered too dangerous. Go comes with a promise of a system language, yet adding memory safety and automatic garbage collection. If you have such needs and you are looking for a language, you may think that Go is the right choice. Maybe it is for you, however, it is not the only choice. Let's consider an alternative. Let's consider Java!

Java as a Language

Java!? That slow, interpreted language which eats enormous amount of memory to execute its virtual machine and feels like an operating system on its own? That Java which every real OS level hacker hates? Well, not exactly that one, but first let's enumerate what kind of language Java is:

Does that sound familiar? Yes, the Java language has the same benefits as attributed to Go language. Moreover those more than twenty years of industry competition for the best Java IDEs and frameworks show: Refactorings, completions, support for various libraries and frameworks make Java ecosystem really rich and strong.

Effective Execution with NativeImage

The classical Java interpreter and JIT compiler comes with significant meta-data overhead not suitable for the embedded use-cases targeted by Go. However, there is a solution: Let's introduce GraalVM and its NativeImage!

NativeImage is an AOT compiler of Java and other JVM languages with smooth interop with C and other OS libraries. NativeImage gives Java the runtime behavior Go provides. In particular NativeImage gives JVM languages the following:

  • instant startup
  • no interpreter/dynamic compilation overhead
  • low memory consumption

If you have a pre-occupations against Java (based on the knowledge of the JIT version) forget them. We'll use the best of Java (or any other JVM language like Kotlin) and combine them with NativeImage to form an ecosystem which addresses the same issues as Go attempts and does it surprisingly well!

The Speed

Comparing speed of various language implementations isn't trivial task and there is usually a lot of room for cheating. On the other hand, there is an independent set of operations (if, while, memory access, memory cleanup) which shall compare so called Turing Speed relatively accurately. I maintain a project to measure Turing Speed of various programming languages on a variant of Ancient and well known Sieve of Eratosthenes algorithm.

Let's compare Go, C and NativeImage Java. What follows are results of https://github.com/jtulach/sieve revision a671eb115 compiled with go1.9.2 on Ubuntu 16.04:

sieve/go$ ./go | grep Hundred
Hundred thousand prime numbers in 253 ms
Hundred thousand prime numbers in 263 ms
Hundred thousand prime numbers in 261 ms
Hundred thousand prime numbers in 270 ms
Hundred thousand prime numbers in 250 ms
Hundred thousand prime numbers in 277 ms
Hundred thousand prime numbers in 241 ms

now change the directory to C version and try the same:

sieve/c$ sieve | grep Hundred
Hundred thousand prime numbers in 100 ms
Hundred thousand prime numbers in 101 ms
Hundred thousand prime numbers in 98 ms
Hundred thousand prime numbers in 102 ms
Hundred thousand prime numbers in 101 ms
Hundred thousand prime numbers in 108 ms
Hundred thousand prime numbers in 97 ms

E.g. Go is slower than C - probably a tax for the safety of the language. Clearly having an automatic memory management must be associated with some cost. Let's try to execute the same algorithm written in Java with NativeImage:

sieve$ mvn -f java/algorithm/ -Dnative.image=/graalvm/bin/native-image package
sieve$ java/algorithm/target/sieve | grep Hundred
Hundred thousand primes computed in 184 ms
Hundred thousand primes computed in 143 ms
Hundred thousand primes computed in 196 ms
Hundred thousand primes computed in 222 ms
Hundred thousand primes computed in 182 ms
Hundred thousand primes computed in 158 ms
Hundred thousand primes computed in 150 ms
Hundred thousand primes computed in 176 ms
Hundred thousand primes computed in 138 ms
Hundred thousand primes computed in 140 ms
Hundred thousand primes computed in 146 ms
Hundred thousand primes computed in 170 ms
Hundred thousand primes computed in 156 ms

Again, some overhead is there. This NativeImage version is also slower than C, but this time at most twice as much. Clearly, in terms of execution speed the Java+NativeImage compilation is at least comparable to the Go version.

Encapsulated Single Binary

One of the great things on Go is its ability to compile everything into a single executable file. This results in faster startup (no dynamic libraries are needed once the executable file is loaded in) and easier portability in embedded systems - enough to transfer the single file, which carries all its environment with it. In our sieve example the size is less than two megabytes:

sieve$ ls -lh go
1,9M go
1,8K sieve.go

The NativeImage compilation works in a similar way. Just like Go compiler, NativeImage also gives you a single binary:

sieve$ mvn -f java/algorithm/ -Dnative.image=/graalvm/bin/native-image package
sieve$ ls -lh java/algorithm/target/sieve
4,8M java/algorithm/target/sieve

The resulting file is slightly bigger (probably as a result of adhering to general Java semantics), but the size remains comparable to the size of the Go executable. The same benefits apply - just copy the file and it will carry everything necessary with it. No need to have multi megabyte JDK installation around. Slim and suitable for your docker or any other restricted system.

Freedom of Choice

Go is a language designed from scratch to solve problems that arise when developing low level OS programs. Go does that quite well. On the other hand, the from scratch design has its drawbacks. The whole Go stack is written by the Go team. It is not based on any industry wide accepted solution. It is not based on LLVM stack. It is not build around GCC infrastructure. It is written by Google from top to bottom. This has a negative effect on features, as well as speed. The team paid for Go support isn't infinite, and thus it cannot address all the incoming requests immediately.

This is not the case of Java, neither NativeImage. JVM has been around for more than twenty years. The HotSpot virtual machine and compiler have been widely accepted by the whole industry and as a result of that enormously optimized. The whole GraalVM stack builds upon that work. So does NativeImage - in fact the AOT compiler behind it is the same as the JIT compiler used in GraalVM HotSpot mode - e.g. the same optimizations that are applied to your code when running on a standard JDK are applicable to NativeImage generated binary.

Another area that benefits from the fierce competition in the last two decades is tooling support. Those twenty years of industry competition for the best Java IDE lead to enormous amount of refactorings, completions, libraries and frameworks. Moreover NativeImage isn't restricted to Java. It processes any JVM language. As such you can code in Scala or Kotlin or any other language that compiles to bytecode enlarging the freedom of choices even more.

Summary

NativeImage provides viable alternative to Go. Not only it benefits from Java ecosystem features, but it even yields faster code, while keeping up on the essential aspects Go was invented for: NativeImage produces single self-contained binary with instant startup and low memory consumption. Java and other JVM languages and NativeImage form a great choice when considering OS-level development.

PS: Next time we look at the interface to C (e.g. operating system API).

Personal tools