Programming

Go는 어떻게 그렇게 빨리 컴파일됩니까?

procodes 2020. 5. 5. 20:50
반응형

Go는 어떻게 그렇게 빨리 컴파일됩니까?


Go 웹 사이트를 둘러 보았지만 Go의 특별한 빌드 시간에 대한 설명을 찾을 수없는 것 같습니다. 언어 기능 (또는 그 부족), 고도로 최적화 된 컴파일러 또는 다른 제품입니까? 나는 Go를 홍보하려고하지 않습니다. 그냥 궁금 해서요


의존성 분석.

로부터 이동 자주 묻는 질문 :

Go는 종속성 분석을 쉽게하고 C 스타일 포함 파일 및 라이브러리의 오버 헤드를 피하는 소프트웨어 구성 모델을 제공합니다.

이것이 빠른 컴파일의 주된 이유입니다. 그리고 이것은 의도적으로 설계된 것입니다.


Go 컴파일러가 빠르다 는 것이 아니라 다른 컴파일러가 느리다고 생각 합니다.

C 및 C ++ 컴파일러는 엄청난 양의 헤더를 구문 분석해야합니다. 예를 들어 C ++ "hello world"를 컴파일하려면 18k 라인의 코드를 컴파일해야합니다.

$ cpp hello.cpp | wc
  18364   40513  433334

Java 및 C # 컴파일러는 VM에서 실행됩니다. 즉, 무엇이든 컴파일하기 전에 운영 체제가 전체 VM을로드해야하며 바이트 코드에서 네이티브 코드로 JIT 컴파일되어야하며 시간이 다소 걸립니다.

컴파일 속도는 몇 가지 요소에 따라 다릅니다.

일부 언어는 빠르게 컴파일되도록 설계되었습니다. 예를 들어, Pascal은 단일 패스 컴파일러를 사용하여 컴파일되도록 설계되었습니다.

컴파일러 자체도 최적화 할 수 있습니다. 예를 들어, Turbo Pascal 컴파일러는 수작업으로 최적화 된 어셈블러로 작성되었으며 언어 설계와 결합되어 286 클래스 하드웨어에서 작동하는 컴파일러가 매우 빠릅니다. 지금까지도 현대 파스칼 컴파일러 (예 : FreePascal)는 Go 컴파일러보다 빠르다고 생각합니다.


Go 컴파일러가 대부분의 C / C ++ 컴파일러보다 훨씬 빠른 여러 가지 이유가 있습니다.

  • 가장 큰 이유 : 대부분의 C / C ++ 컴파일러는 컴파일 속도 관점에서 볼 때 예외적으로 나쁜 디자인을 나타냅니다. 또한 컴파일 속도 측면에서 C / C ++ 에코 시스템의 일부 (예 : 프로그래머가 코드를 작성하는 편집기)는 컴파일 속도를 염두에두고 설계되지 않았습니다.

  • 가장 큰 이유 : 빠른 컴파일 속도는 Go 컴파일러와 Go 언어에서 의식적인 선택이었습니다.

  • Go 컴파일러는 C / C ++ 컴파일러보다 간단한 옵티 마이저를 가지고 있습니다.

  • C ++와 달리 Go에는 템플릿과 인라인 함수가 없습니다. 즉, Go에서 템플릿 또는 함수 인스턴스화를 수행 할 필요가 없습니다.

  • Go 컴파일러는 하위 수준의 어셈블리 코드를 더 빨리 생성하고 옵티마이 저는 어셈블리 코드에서 작동하는 반면, 일반적인 C / C ++ 컴파일러에서는 최적화 패스가 원래 소스 코드의 내부 표현에서 작동합니다. C / C ++ 컴파일러의 추가 오버 헤드는 내부 표현을 생성해야한다는 사실에서 비롯됩니다.

  • Go 컴파일러는 사용 된 모든 어셈블리 코드를 거치고 C / C ++와 같은 다른 추가 작업을 수행하기 때문에 Go 프로그램의 최종 링크 (5l / 6l / 8l)는 C / C ++ 프로그램을 링크하는 것보다 느릴 수 있습니다. 링커는하지 않습니다

  • 일부 C / C ++ 컴파일러 (GCC)는 텍스트 형식 (어셈블러에 전달됨)으로 명령어를 생성하는 반면 Go 컴파일러는 이진 형태로 명령어를 생성합니다. 텍스트를 이진으로 변환하려면 추가 작업을 수행해야합니다.

  • Go 컴파일러는 소수의 CPU 아키텍처 만 대상으로하는 반면 GCC 컴파일러는 많은 수의 CPU를 대상으로합니다.

  • Jikes와 같은 빠른 컴파일 속도를 목표로 설계된 컴파일러는 빠릅니다. 2GHz CPU에서 Jikes는 초당 20000+ 라인의 Java 코드를 컴파일 할 수 있습니다 (그리고 증분 컴파일 모드는 훨씬 더 효율적입니다).


컴파일 효율성은 주요 디자인 목표였습니다.

마지막으로 빠른 속도를 목표로합니다. 단일 컴퓨터에서 큰 실행 파일을 빌드하는 데 최대 몇 초가 걸립니다. 이러한 목표를 달성하기 위해서는 많은 언어 문제를 다루어야했다. 동시성 및 가비지 콜렉션; 엄격한 의존성 사양; 등등. 자주하는 질문

언어 FAQ는 구문 분석과 관련된 특정 언어 기능과 관련하여 매우 흥미 롭습니다.

둘째, 언어는 분석하기 쉽도록 설계되었으며 기호 테이블없이 구문 분석 할 수 있습니다.


위의 대부분은 사실이지만 실제로 언급하지 않은 중요한 점은 종속성 관리입니다.

이동은 당신이 가져 오는 것을 패키지를 포함 할 필요가 직접 (그 이미 어떤 수입으로 그들이 필요). 이것은 모든 단일 파일 이 y 헤더 등을 포함하는 x 헤더를 포함하여 시작 하는 C / C ++와 완전히 대조적입니다 . 결론 : Go의 컴파일은 가져온 패키지 수에 선형 시간이 걸리고 C / C ++는 지수 시간이 걸립니다.


컴파일러의 변환 효율성에 대한 좋은 테스트는 자체 컴파일입니다. 주어진 컴파일러가 자체 컴파일하는 데 얼마나 걸립니까? C ++의 경우 시간이 오래 걸립니다 (시간?). 이에 비해 Pascal / Modula-2 / Oberon 컴파일러는 최신 머신에서 1이내에 컴파일됩니다 [1].

Go는 이러한 언어에서 영감을 얻었지만 이러한 효율성의 주요 원인은 다음과 같습니다.

  1. 효율적인 스캔 및 구문 분석을 위해 수학적으로 명확하게 정의 된 구문입니다.

  2. 형태 보증 및 정적으로 컴파일 된 언어가 사용하는 별도의 컴파일 의존성 및 유형 검사 를 통해 방지하기 위해, 모듈 경계 불필요한 다시 읽기 헤더 파일과 다른 모듈의 재 컴파일하는 - 반대로 독립 ++ 여기서 C / C에서와 같이 컴파일 컴파일러는 이러한 교차 모듈 검사를 수행하지 않습니다 (따라서 간단한 한 줄 "hello world"프로그램의 경우에도 모든 헤더 파일을 반복해서 다시 읽어야합니다).

  3. 효율적인 컴파일러 구현 (예 : 단일 패스, 재귀 하향 하향식 구문 분석)-물론 위의 1 및 2 지점에서 크게 도움이됩니다.

이러한 원칙은 1970 년대와 1980 년대에 Mesa, Ada, Modula-2 / Oberon 및 기타 여러 언어로 이미 알려져 있고 완전히 구현되었으며 현재는 (2010 년대) Go (Google)와 같은 현대 언어로의 길을 찾고 있습니다. , Swift (Apple), C # (Microsoft) 및 기타 여러 가지.

이것이 곧 예외가 아닌 표준이 되길 바랍니다. 거기에 가려면 두 가지 일이 발생해야합니다.

  1. 먼저, Google, Microsoft 및 Apple과 같은 소프트웨어 플랫폼 제공 업체는 애플리케이션 개발자가 새로운 코드 작성 방법을 사용 하도록 권장 하면서 기존 코드 기반을 재사용 할 수 있도록해야합니다. 이것이 바로 동일한 런타임 환경을 사용하기 때문에 Objective-C와 공존 할 수있는 Swift 프로그래밍 언어로 Apple이 시도하는 것입니다.

  2. 둘째, 기본 소프트웨어 플랫폼 자체는 결국 이러한 원칙을 사용하여 시간이 지남에 따라 다시 작성되어야하며 동시에 모듈 식을 모 놀리 식으로 만들도록 모듈 계층 구조를 재 설계해야합니다. 이것은 물론 굉장한 작업이며 10 년 동안 더 잘 할 수 있습니다 (실제로 그렇게 할 수있을 정도로 용기가 있다면-Google의 경우 전혀 확실하지 않습니다).

In any case, it's the platform that drives language adoption, and not the other way around.

References:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf, page 6: "The compiler compiles itself in about 3 seconds". This quote is for a low cost Xilinx Spartan-3 FPGA development board running at a clock frequency of 25 MHz and featuring 1 MByte of main memory. From this one can easily extrapolate to "less than 1 second" for a modern processor running at a clock frequency well above 1 GHz and several GBytes of main memory (i.e. several orders of magnitude more powerful than the Xilinx Spartan-3 FPGA board), even when taking I/O speeds into account. Already back in 1990 when Oberon was run on a 25MHz NS32X32 processor with 2-4 MBytes of main memory, the compiler compiled itself in just a few seconds. The notion of actually waiting for the compiler to finish a compilation cycle was completely unknown to Oberon programmers even back then. For typical programs, it always took more time to remove the finger from the mouse button that triggered the compile command than to wait for the compiler to complete the compilation just triggered. It was truly instant gratification, with near-zero wait times. And the quality of the produced code, even though not always completely on par with the best compilers available back then, was remarkably good for most tasks and quite acceptable in general.


Go was designed to be fast, and it shows.

  1. Dependency Management: no header file, you just need to look at the packages that are directly imported (no need to worry about what they import) thus you have linear dependencies.
  2. Grammar: the grammar of the language is simple, thus easily parsed. Although the number of features is reduced, thus the compiler code itself is tight (few paths).
  3. No overload allowed: you see a symbol, you know which method it refers to.
  4. It's trivially possible to compile Go in parallel because each package can be compiled independently.

Note that GO isn't the only language with such features (modules are the norm in modern languages), but they did it well.


The basic idea of compilation is actually very simple. A recursive-descent parser, in principle, can run at I/O bound speed. Code generation is basically a very simple process. A symbol table and basic type system is not something that requires a lot of computation.

However, it is not hard to slow down a compiler.

If there is a preprocessor phase, with multi-level include directives, macro definitions, and conditional compilation, as useful as those things are, it is not hard to load it down. (For one example, I'm thinking of the Windows and MFC header files.) That is why precompiled headers are necessary.

In terms of optimizing the generated code, there is no limit to how much processing can be added to that phase.


Quoting from the book "The Go Programming Language" by Alan Donovan and Brian Kernighan:

Go compilation is notably faster than most other compiled languages, even when building from scratch. There are three main reasons for the compiler’s speed. First, all imports must be explicitly listed at the beginning of each source file, so the compiler does not have to read and process an entire file to determine its dependencies. Second, the dependencies of a package form a directed acyclic graph, and because there are no cycles, packages can be compiled separately and perhaps in parallel. Finally, the object file for a compiled Go package records export information not just for the package itself, but for its dependencies too. When compiling a package, the compiler must read one object file for each import but need not look beyond these files.


Simply ( in my own words ), because the syntax is very easy ( to analyze and to parse )

For instance, no type inheritance means, not problematic analysis to find out if the new type follows the rules imposed by the base type.

For instance in this code example: "interfaces" the compiler doesn't go and check if the intended type implement the given interface while analyzing that type. Only until it's used ( and IF it is used ) the check is performed.

Other example, the compiler tells you if you're declaring a variable and not using it ( or if you are supposed to hold a return value and you're not )

The following doesn't compile:

package main
func main() {
    var a int 
    a = 0
}
notused.go:3: a declared and not used

This kinds of enforcements and principles make the resulting code safer, and the compiler doesn't have to perform extra validations that the programmer can do.

At large all these details make a language easier to parse which result in fast compilations.

Again, in my own words.


i think Go was designed in parallel with compiler creation, so they were best friends from birth. (IMO)

참고URL : https://stackoverflow.com/questions/2976630/how-does-go-compile-so-quickly

반응형