Go

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Forget Go!)
Current revision (05:38, 26 March 2022) (edit) (undo)
 
(29 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 [[wikipedia:garbage collection (computer science)|garbage collection]]
+
* 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 managed to keep some of its [[coolness]]. Primarily because of the rise of [[docker]] (as most of the [[docker]] ecosystem is written in [[Go]]).
+
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]]! ===
-
The above is probably all you need to know about [[Go]]. Because the goal of this post isn't to promote [[Go]]. The post was written to explain that you don't need [[Go]] at all. There are better, faster, more approachable, more toolable alternative [[language]]s. If you are a happy [[Go]] user, stick to it, but if you are considering to use [[Go]] for development of a new system, then the main take away should be: ''Forget Go!'', there is a better way.
+
[[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]]!
-
Let's start by enumerating what are the problems with the [[Go]] language.
+
==== [[Java]] as a [[Language]] ====
-
==== Slow ====
+
[[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]] is slow. The same algorithm written in [[Go]] runs at least twice as slow than the same algorithm written in [[Java]] or [[C]]. 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. What follows are results of https://github.com/jtulach/sieve revision a671eb115 compiled with go1.9.2 on [[Ubuntu]] 16.04:
+
* [[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>
-
Can anybody [[Talkback|explain to me]] what is so interesting on a language 2.5x times slower than [[C]]!?
+
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]]:
-
 
+
-
==== Hard for Coding ====
+
-
 
+
-
One of the original [[Go]] mottos was to [[wikipedia::Go_(programming_language)#History|not require integrated development environments]]. Obviously one can code with '''vi''' or '''sed''', but at cost of complete loss of productivity. Thus I doubt the IDE-less vision materialized. Definitely not more than for [[C]].
+
-
 
+
-
Coding, editing, compiling, fixing errors, debugging when writing [[Go]] code feels to me like editing [[Java]] twenty years ago (at that time [[I]] was writing the 1st [[IDE]] for [[Java]] - [[Xelfi]] which later became [[NetBeans]] - obviously there was no [[IDE]] for [[Java]] at that time). Do your edits in '''notepad''' or etc., go to command line, compile, read the errors, locate them in the editor, try to fix them (without any hint about the [[Go]] [[API]]s), repeat until the [[Go]] compiler is happy and you get your executable. Run to get a failure. Try to seek for a debugger. No, it is not '''gdb''', search more. Give up, and resort to '''printf''' logging. Honestly, it is a disaster to usability for everyone spoiled by enormous competition between [[IDE]]s for [[Java]] in the last twenty years. But maybe the whole story shouldn't be that dark. Maybe there are people productive with [[Go]] - especially if you want to hire the few most expensive experts that code [[Go]] for living...
+
-
 
+
-
However from perspective of a regular [[StackOverFlow]] developer, the tooling is really bad. There is an [[IDE]] that supports [[Go]], but even its basic editing features aren't for free. There is an implementation of [[LSP]] server, but your mileage may vary. Looks like [[Go]] is years behind any language adopted by the industry.
+
-
 
+
-
==== Proprietary ====
+
-
 
+
-
[[Go]] is proprietary. The whole stack is written from scratch and not based only anything the industry shares. 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. Yet it needs to reinvent everything (including a wheel) again due to uniqueness of the overall [[Go]] language/compiler implementation. As a result [[Go]] falls behind in many respects compared to [[language]]s well adopted by the industry.
+
-
 
+
-
This is not to say everything in bad in [[Go]]. If you are working in an ecosystem that is build with [[Go]] (e.g. you are coding against [[Docker]] [[API]]s or you are a [[Google]] employee), it may still be beneficial to use [[Go]]. But otherwise, let's consider [[Java]]!
+
-
 
+
-
=== [[Go]], [[Java]] [[go]]! ===
+
-
 
+
-
[[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? Yes, that one. Well, not exactly that [[Java]], but rather a [[SubstrateVM]] - an ahead of time compiler. Let's enumerate what [[Java]] is:
+
-
 
+
-
* [[wikipedia:static typing|statically typed]]
+
-
* [[wikipedia:compiled language|compiled]] language
+
-
** into a [[bytecode]]
+
-
** or with [[SubstrateVM]] in the tradition of [[C]]
+
-
* with [[wikipedia:memory safety|memory safety]]
+
-
* and [[wikipedia:garbage collection (computer science)|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]]. Moreover (in combination with [[SubstrateVM]]) one also gets similar runtime behavior. [[SubstrateVM]] gives [[Java]] (and any other [[JVM]] language) following:
+
-
 
+
-
* instant startup
+
-
* no interpreter/dynamic compilation overhead
+
-
* low memory consumption
+
-
 
+
-
If you have a pre-occupations against [[Java]] forget them. We'll use the best of [[Java]] (or any other [[JVM]] language like [[Kotlin]]) and combine them with [[SubstrateVM]] to form an ecosystem which is clearly better than anything [[Go]] can provide.
+
-
 
+
-
==== [[Java]] is Fast ====
+
-
 
+
-
Running the same Sieve of Eratosthenes algorithm with standard [[Java]] 1.8 gives you:
+
-
 
+
-
<source lang="bash">
+
-
sieve$ mvn -f java/algorithm/ package exec:java | grep Hundred
+
-
Hundred thousand primes computed in 100 ms
+
-
Hundred thousand primes computed in 108 ms
+
-
Hundred thousand primes computed in 93 ms
+
-
Hundred thousand primes computed in 90 ms
+
-
Hundred thousand primes computed in 84 ms
+
-
Hundred thousand primes computed in 87 ms
+
-
Hundred thousand primes computed in 85 ms
+
-
Hundred thousand primes computed in 84 ms
+
-
Hundred thousand primes computed in 84 ms
+
-
Hundred thousand primes computed in 84 ms
+
-
Hundred thousand primes computed in 86 ms
+
-
</source>
+
-
 
+
-
with [[SubstrateVM]] it is slightly slower, but still faster than [[Go]]:
+
<source lang="bash">
<source lang="bash">
Line 122: Line 91:
</source>
</source>
-
==== [[SubstrateVM]] Gives You single Binary ====
+
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.
-
Just like [[Go]] compiler, also [[SubstrateVM]] gives you a single binary which can be used and deployed to your [[docker]] or any other restricted system:
+
==== 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">
<source lang="bash">
Line 132: Line 111:
</source>
</source>
-
That single 4.8MB file is all you need. That is a comparable size to [[Go]] result which gives you file roughly of 2MB size. Certainly way smaller than trying to pack the standard [[HotSpot]] [[JDK]].
+
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.
-
==== [[Java]] Requires Little of Memory ====
+
{{#ev:youtube|TJU3zy4b-Iw}}
-
 
+
-
[[TBD]]: Needs some explanation or tuning:
+
-
 
+
-
<source lang="bash">
+
-
PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
+
-
20 0 277680 267388 4204 R 100,0 1,7 0:12.78 sieve.java
+
-
20 0 9016 6356 1132 S 100,7 0,0 0:14.22 sieve.go
+
-
</source>
+
-
==== The Tooling for [[Java]] ====
+
==== Freedom of Choice ====
-
Clearly nothing compares to twenty years of industry competition for the best [[Java]] [[IDE]]. Refactorings, completions, support for various libraries and frameworks. This is not something the magnitude smaller [[Go]] ecosystem can compete with. With [[Java]] you can easily reuse the existing skills of millions of developers without teaching them new tricks and [[language]] from the ground. You can build on shoulders of giants.
+
[[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.
-
Moreover [[SubstrateVM]] 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 options even more.
+
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.
-
== Summary ==
+
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.
-
[[SubstrateVM]] makes [[Java]] competitive with [[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: [[SubstrateVM]] produces single '''self-contained''' binary with '''instant''' startup and '''low memory''' consumption.
+
== Go [[Java]], Go! ==
-
[[Java]] and [[SubstrateVM]] is a great choice and full replacement of [[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]]).
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:

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.

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).

Personal tools
buy