Go
From APIDesign
(→Hard for Coding) |
|||
(51 intermediate revisions not shown.) | |||
Line 4: | Line 4: | ||
* [[wikipedia:compiled language|compiled]] language in the tradition of [[C]] | * [[wikipedia:compiled language|compiled]] language in the tradition of [[C]] | ||
* with [[wikipedia:memory safety|memory safety]] | * with [[wikipedia:memory safety|memory safety]] | ||
- | * and [[ | + | * and [[Garbage Collection]] |
* and [[wikipedia:structural type system|structural typing]] | * and [[wikipedia:structural type system|structural typing]] | ||
* and [[wikipedia:communicating sequential processes|CSP]]-style [[wikipedia:concurrency (computer_science)|concurrency]] | * and [[wikipedia:communicating sequential processes|CSP]]-style [[wikipedia:concurrency (computer_science)|concurrency]] | ||
- | After the usual [[good|initial hype]] it | + | After the usual [[good|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]]). |
=== Forget [[Go]]! === | === 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 [[JVM|virtual machine]] and feels like an [[OS|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: | |
- | [[Go]] | + | * [[wikipedia:static typing|statically typed]] |
+ | * [[wikipedia:compiled language|compiled]] language | ||
+ | ** into a [[bytecode]] | ||
+ | ** or with [[NativeImage]] into native code in the tradition of [[C]] | ||
+ | * with [[wikipedia:memory safety|memory safety]] | ||
+ | * and [[Garbage Collection]] | ||
+ | * and [[OOP|object oriented typing]] | ||
+ | * with [[Monitor|multi-threading concepts]] built into the [[language]] since day one | ||
+ | |||
+ | 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]] [[IDE]]s 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]] [[language]]s with smooth interop with [[C]] and other [[OS]] libraries. [[NativeImage]] gives [[Java]] the runtime behavior [[Go]] provides. In particular [[NativeImage]] gives [[JVM]] [[language]]s 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 [https://github.com/jtulach/sieve project] to measure [[Turing Speed]] of various programming [[language]]s 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: | ||
<source lang="bash"> | <source lang="bash"> | ||
Line 44: | Line 71: | ||
</source> | </source> | ||
- | + | 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]]: | |
- | == | + | <source lang="bash"> |
+ | 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 | ||
+ | </source> | ||
- | + | 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: | |
- | + | <source lang="bash"> | |
- | + | sieve$ ls -lh go | |
- | + | 1,9M go | |
+ | 1,8K sieve.go | ||
+ | </source> | ||
- | + | The [[NativeImage]] compilation works in a similar way. Just like [[Go]] compiler, [[NativeImage]] also gives you a single binary: | |
- | = | + | <source lang="bash"> |
- | + | 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 | |
- | + | </source> | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | 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. | |
- | + | {{#ev:youtube|TJU3zy4b-Iw}} | |
- | + | ==== 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. | |
- | [[ | + | == Go [[Java]], Go! == |
- | + | [[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]] [[language]]s 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]]). |
Current revision
Go is a programming language developed by Google. When it was introduced in 2009, it was promoted as:
- statically typed
- compiled language in the tradition of C
- with memory safety
- and Garbage Collection
- and structural typing
- and CSP-style concurrency
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:
- statically typed
- compiled language
- into a bytecode
- or with NativeImage into native code in the tradition of C
- with memory safety
- and Garbage Collection
- and object oriented typing
- with multi-threading concepts built into the language since day one
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.
Go Java, Go!
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).