Why does Go programs compile faster than Java or C#?

1,881 views
Skip to first unread message

Erik Engheim

unread,
Mar 25, 2014, 7:18:34 AM3/25/14
to golan...@googlegroups.com
Hi, I wanted to do a presentation at work about Go, and I know one of the "features" of Go is fast compile times.  So I'd like to understand better what the reason for this is. Usually I see comparisons with C/C++ but they have bad compile times for so many obvious reasons (headers, templates, macros, complicated grammar etc). However it is not clear to me why Go would compile any faster than Java or C#.

Some of the points I am curious about are:

1. Go is a fairly simple language to parse. But how important is that? Java isn't that complicated to parse either or is it?
2. Dependency analysis is often mention. Is this really any different in Go from Java or C#? If so how?
3. No generics. Does this matter much? If so why?
4. I've read somewhere that dependency analysis or checks are easier in Go because there are no inheritance hierarchy. Can anybody elaborate on this or provide some links?
5. To what degree does Go compile due to the design of the language vs the implementers were just really good at making compilers or it compiles fast just because few optimisations are done.

Are there any good resource online for reading about compiler design with respect to dependency analysis, tradeoffs in compilation speed vs language features etc?

Michael Jones

unread,
Mar 25, 2014, 7:32:06 AM3/25/14
to Erik Engheim, golang-nuts


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Michael T. Jones | Chief Technology Advocate  | m...@google.com |  +1 650-335-5765

Erik Engheim

unread,
Mar 25, 2014, 7:36:32 AM3/25/14
to Michael Jones, golang-nuts
Thanks, but I’ve looked at this answer already.  It doesn’t say anything about Java and C# really. It mentions dependency analysis but doesn’t say anything about how it differs from say Java. Whenever I see this compared, it is compared to C++, which obviously falls short with the header files. I just don’t get how dependency analysis is easier or better than in Java (or C# for that matter).

Rob Pike

unread,
Mar 25, 2014, 7:49:21 AM3/25/14
to Erik Engheim, Michael Jones, golang-nuts
There's a lot of hooey in that thread. Here are some real reasons:

1) There is less code to compile.
1a) (Corollary) Dependency management is exponentially better in Go
than in C and (with even higher exponent) C++.
2) Other compilers are slow more than the Go compiler is fast. (The
compiler hasn't even been properly tuned for speed. A truly fast
compiler could generate the same quality code much faster.)
3) The gc compiler is simpler code compared to most recent compilers.
4) The gc compiler is written in straightforward C, not Java or C++ or
more heavyweight languages.
5) The gc compiler does not have a state-of-the-art optimizer.
6) Go programs are compiled once. Languages with virtual machines
compile the code twice.

The oft-cited claim that Go's syntax is regular so is easy to parse is
not a significant factor for compilation speed.

-rob

Michael Jones

unread,
Mar 25, 2014, 7:49:56 AM3/25/14
to Erik Engheim, golang-nuts
Go's import mechanism is clever. The magic info that is imported by that reference contains all the information about the entire tree of calls that package makes. So, if you have 1000 files in 50 packages of 20 files, the compiler ends up reading 50 import definitions rather than 1000. That helps.

The compiler suite is "simple" compared to the GCC-class compilers. The latter are designed for maximum generality, extensible machine definitions, extensible optimizations of an intermediate code, and extensible back-ends for various machine architectures. This is the power/genius of GCC, and its weakness. Indirection, meta-data, intermediate forms, extensions, and the rest all cost.

The compiler suite is simple in its optimizations/register assignment/and most of all, data liveness analysis. GCC basically does every known thing, with each extension coming from someone's thesis. Most of these are "slow compile but great code" tradeoffs. (Like the Diophantine solver to prove that loop indices/array accesses will or will not collide) Nothing wrong with that. But they are slower because of it.

C has CPP and the mess of a forest of include files. C++ has the same, and also very powerful macro processing/metaprogramming in the language. Refer to BOOST for proof of this. I think that power is wonderful. The Go architects are wary of it's misuse and cost in compilation. In any case, the power comes at the cost of slower compile time.

The Go compilers come from Ken Thompson. He chose to be fast and can implement whatever he desires. (You can almost see his halo when you meet him in person.)

These are some of the reasons.

Erik Engheim

unread,
Mar 25, 2014, 8:00:43 AM3/25/14
to Rob Pike, Michael Jones, golang-nuts
Wow, thanks for such a quick answer Rob. If you don’t mind I have a couple of follow up questions to your answers.

On 25 Mar 2014, at 12:49, Rob Pike <r...@golang.org> wrote:

> There's a lot of hooey in that thread. Here are some real reasons:
>
> 1) There is less code to compile.
> 1a) (Corollary) Dependency management is exponentially better in Go
> than in C and (with even higher exponent) C++.

I assume you refer to the accumulation of header files in C/C++. Does “Less code to compile” also hold for languages such as C# and Java?

> 2) Other compilers are slow more than the Go compiler is fast. (The
> compiler hasn't even been properly tuned for speed. A truly fast
> compiler could generate the same quality code much faster.)
> 3) The gc compiler is simpler code compared to most recent compilers.
> 4) The gc compiler is written in straightforward C, not Java or C++ or
> more heavyweight languages.

Yeah, that was actually one of the things I wondered about. I am not sure if you can answer this, but I have seen threads from a few years back talking about how Java compilation was slow because all these JVM processes got spawned, and each time you had to pay the price of the slow startup of a virtual machine. I couldn’t find out if that was the case still or if this is substantially different for JIT.

> 5) The gc compiler does not have a state-of-the-art optimizer.
> 6) Go programs are compiled once. Languages with virtual machines
> compile the code twice.
>
> The oft-cited claim that Go's syntax is regular so is easy to parse is
> not a significant factor for compilation speed.
>
How about semantics? Inheritance, package dependencies, no overriding of functions etc

> -rob

Erik Engheim

unread,
Mar 25, 2014, 9:30:10 AM3/25/14
to Michael Jones, golang-nuts
Thanks for the details.

Just to clarify...
On 25 Mar 2014, at 12:49, Michael Jones <m...@google.com> wrote:

Go's import mechanism is clever. The magic info that is imported by that reference contains all the information about the entire tree of calls that package makes. So, if you have 1000 files in 50 packages of 20 files, the compiler ends up reading 50 import definitions rather than 1000. That helps.

What you mean is that if my Go program imports these 50 packages, then when I compile my program it will only look at the functions and types exported by these 50 packages. It will not look at all those other functions exposed by the other 1000 files?

E.g. if I have two packages circle and point, and I have a main package for my program which only includes circle.

circle package:

import . “point”

type Circle struct {
   Center Point
   Radius float64
}

point package:

type Point struct {
   X, Y float64
}

main package

import “circle”

Then will the compiled circle package only contain a definition for Circle and not Point? Meaning if main.go wants to do stuff with Point objects it needs to import the point package? Wouldn’t this be similar to using a forward declaration of  Point in C++. If the circle header file doesn’t include the point header file I don’t have to pay for that either in C++.

I am sorry if I am dense about this. But I am a C++ guy so I don’t really have a good mental model for how this sort of thing works without header files.

Ian Lance Taylor

unread,
Mar 25, 2014, 9:38:04 AM3/25/14
to Erik Engheim, Michael Jones, golang-nuts
On Tue, Mar 25, 2014 at 6:30 AM, Erik Engheim <erik.e...@gmail.com> wrote:
>
> On 25 Mar 2014, at 12:49, Michael Jones <m...@google.com> wrote:
>
> Go's import mechanism is clever. The magic info that is imported by that
> reference contains all the information about the entire tree of calls that
> package makes. So, if you have 1000 files in 50 packages of 20 files, the
> compiler ends up reading 50 import definitions rather than 1000. That helps.
>
>
> What you mean is that if my Go program imports these 50 packages, then when
> I compile my program it will only look at the functions and types exported
> by these 50 packages. It will not look at all those other functions exposed
> by the other 1000 files?
>
> E.g. if I have two packages circle and point, and I have a main package for
> my program which only includes circle.
>
> circle package:
>
> import . “point”
>
> type Circle struct {
> Center Point
> Radius float64
> }
>
> point package:
>
> type Point struct {
> X, Y float64
> }
>
> main package
>
> import “circle”
>
> Then will the compiled circle package only contain a definition for Circle
> and not Point?

What matters here is specifically the export information. The export
information will in fact include a definition for Point, although it
will not be accessible from the main package. The definition is
needed in order to describe the circle.Circle type. But the export
information has been precompiled, so reading it is faster than parsing
the original file.

> Wouldn’t this be similar to using a
> forward declaration of Point in C++. If the circle header file doesn’t
> include the point header file I don’t have to pay for that either in C++.

Yes, but in your example that is not possible, because Circle has a
field of type Point. If it had a field of type *Point then a forward
declaration of Point would suffice (in either C++ or Go).

> I am sorry if I am dense about this. But I am a C++ guy so I don’t really
> have a good mental model for how this sort of thing works without header
> files.

Compiling a package produces export information that describes all the
exported names in the package. Importing a package reads that export
information.

Ian

Erik Engheim

unread,
Mar 25, 2014, 10:12:40 AM3/25/14
to Ian Lance Taylor, Michael Jones, golang-nuts
Thanks for taking the time to explain this, I think I might understand it now. This is how I understand it so far:
When compiling a C++ program with files foo.cpp and bar.cpp which both include circle.h, we get the problem that we add the source code for circle.h and point.h twice. Compiling circle.cpp doesn’t leave any kind of binary structure describing exported functions which may be used by both foo.cpp and bar.cpp. But with Go once circle.go has been compiled, the job is done. Both foo.go and bar.go can use the same compiled file.

But I assumed that in have if foo.java and bar.java imported circle package then they also would could use the same compiled circle.class file. But I guess possible circular dependency might might it more complicated in Java.

Ian Lance Taylor

unread,
Mar 25, 2014, 11:58:51 AM3/25/14
to Erik Engheim, Michael Jones, golang-nuts
On Tue, Mar 25, 2014 at 7:12 AM, Erik Engheim <erik.e...@gmail.com> wrote:
>
> But I assumed that in have if foo.java and bar.java imported circle package
> then they also would could use the same compiled circle.class file. But I
> guess possible circular dependency might might it more complicated in Java.

I don't know how Java compilers work. But, yes, since Java appears to
permit circular import dependencies, whatever it does must be more
complex than what Go does.

I don't know about C#.

Either way, Java and C# are not directly comparable to Go, as Go
compiles to machine code but Java and C# compile to byte code. In
most modern VMs there is then a second optional compilation phase at
run time.

Ian

oju...@gmail.com

unread,
Mar 25, 2014, 1:41:18 PM3/25/14
to golan...@googlegroups.com, Erik Engheim, Michael Jones
On Tuesday, March 25, 2014 12:58:51 PM UTC-3, Ian Lance Taylor wrote:
...

Either way, Java and C# are not directly comparable to Go, as Go
compiles to machine code but Java and C# compile to byte code.  In
most modern VMs there is then a second optional compilation phase at
run time.
 
Nowadays some VMs perform even a 3rd compilation phase. So we have:

1st - source code to bytecode (at development time);
2nd - bytecode to non optimized native code (at runtime, compiles fast but runs slowly);
3rd - bytecode to optimized native code (at runtime, compiles slowly but runs fast);

This last compilation phase is executed only for performance critical methods.

Reply all
Reply to author
Forward
0 new messages