Overview
When I started learning the Go language, I already had experience with three statically typed languages—C/C++ and Java—and two dynamically typed languages—PHP and JavaScript. Because of this background, when I compiled a demo file of several hundred lines for the first time in Go, I was genuinely impressed by its compilation speed. At that moment, I thought to myself, "Go claims to have the execution speed of statically typed languages and the compilation speed of dynamically typed languages—it indeed lives up to its reputation." Indeed, one of the primary motivations behind Google inventing Go was to solve the problem of lengthy build times for large-scale projects internally. Leaving aside the historical context of the technology's development, this article focuses on the main optimization strategies behind Go's compilation speed.
Minimal Keywords
Go has only 25 keywords, which helps shorten compilation time.
Symbol Table
A symbol table is a data structure in the compiler used to store identifiers in the program (such as variables, functions, structs/objects), mainly serving to provide a way to manage these identifiers efficiently for referencing, resolving, optimizing, etc., during different stages of compilation.
In Go, there isn't a direct (explicit) symbol table; instead, the compiler creates some internal data structures to manage identifiers and type information during compilation. Although these internal data structures aren't directly exposed to developers as a symbol table, they can be considered the compiler's "internal symbol table." Additionally, Go provides reflection mechanisms that allow retrieving types, struct fields, calling methods/stacks, etc., at runtime, as well as accessing and modifying object fields. Developers can obtain similar information to a symbol table through this indirect means.
Dependency Analysis
This is the main reason for the performance boost in Go's compilation speed.
Eliminating Circular Dependencies
When circular dependencies appear in Go, the compiler will report an error. Once developers resolve the circular dependencies, the compilation process needs only to recurse to the bottom of the dependency tree, essentially a Depth-First Search (DFS) process. Furthermore, under the condition of no circular dependencies, each package can be compiled independently and in parallel.
Simplifying Dependencies
In Go code files, you only need to include the names of packages that are directly used in the code. For example, when needing to use the Cookie object, you only need to import the cookiejar
package and not the http
package.
package main
import "net/http/cookiejar"
func main() {
var c cookiejar.Jar
}
At the same time, importing unused packages in Go will result in a compilation error:
imported and not used: ...
This pre-compilation constraint ensures that unrelated packages do not affect compilation time.
Lastly, Go requires that all packages used in the source file must be listed at the beginning of the file (after the `package` statement). Thus, the compiler does not need to read the entire file to determine dependencies. The target files of already compiled Go packages record not only the exported information of the package but also the exported information of the packages it depends on. When compiling, you only need to import a target file without checking other information.
No Support for Overloading/Rewriting
Go does not support method rewriting or method overloading, meaning all methods can be considered statically typed, similar to static methods in Java.
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
No Support for Templates
Go also does not support template methods, which avoids the performance overhead brought by template instantiation.
No Need for a Virtual Machine
Like Java, Go is a language with built-in Garbage Collection (GC), but unlike Java, which compiles within a virtual machine, Java code must be compiled into bytecode before passing through the compiler. However, Go does not depend on a virtual machine to compile code but rather compiles directly from source code to binary files.
Translated from https://dbwu.tech/posts/golang/why_golang_compiles_so_fast/
You forgot one of the most important when developing locally: package-level caching!