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


217

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


12
@Support, 알고 있습니다. 필자는 빠른 속도로 컴파일하는 방식으로 컴파일러를 구현하는 것이 조기 최적화에 불과하다고 생각합니다. 이는 훌륭한 소프트웨어 설계 및 개발 사례의 결과 일 가능성이 높습니다. 또한, Knuth의 단어가 문맥에서 벗어 났고 잘못 적용되는 것을 볼 수 없습니다.
Adam Crossland

55
이 질문의 비관론자 버전은 "왜 C ++이 그렇게 느리게 컴파일 되는가?"입니다. stackoverflow.com/questions/588884/…
dan04

14
나는이 질문이 의견에 근거하지 않기 때문에이 질문을 다시 열기로 투표했습니다. 컴파일 속도를 결정하는 언어 및 / 또는 컴파일러 선택에 대한 우수한 기술적 (비 선택적) 개요를 제공 할 수 있습니다.
Martin Tournoij

소규모 프로젝트의 경우 Go가 느려 보입니다. 터보-파스칼이 수천 배 느려진 컴퓨터에서 훨씬 빠르다는 것을 기억하기 때문입니다. prog21.dadgum.com/47.html?repost=true . "go build"를 입력 할 때마다 몇 초 동안 아무 일도 일어나지 않습니다. 나는 오래된 Fortran 컴파일러와 펀치 카드로 되돌아갑니다. YMMV. TLDR : "느린"및 "빠른"은 상대적인 용어입니다.
RedGrittyBrick

답변:


193

의존성 분석.

이동 자주 묻는 질문은 다음과 같은 문장을 포함하는 데 사용 :

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

이 문구는 더 이상 FAQ에 없지만이 주제는 Google 토크 대화에서 자세히 설명합니다.이 주제는 C / C ++ 및 Go의 종속성 분석 접근 방식을 비교합니다.

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


이 문구는 더 이상 Go FAQ에 없지만 C / C ++와 Pascal / Modula / Go 접근법을 비교하는 "종속성 분석"주제에 대한 자세한 설명은 Google
rob74

76

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 컴파일러보다 빠르다고 생각합니다.


19
Microsoft의 C # 컴파일러는 VM에서 실행되지 않습니다. 주로 성능상의 이유로 C ++로 작성되었습니다.
blucz

19
터보 파스칼 이상 델파이는 엄청나게 빠른 컴파일러의 가장 좋은 예입니다. 두 설계자가 모두 Microsoft로 마이그레이션 한 후 MS 컴파일러와 언어가 크게 향상되었습니다. 그것은 우연의 일치가 아닙니다.
TheBlastOne

7
코드의 18k 라인 (정확히는 18364)은 433334 바이트 (~ 0,5MB)입니다.
el.pescado

9
C # 컴파일러는 2011 년부터 C #으로 컴파일되었습니다. 나중에 읽을 수 있도록 업데이트했습니다.
커트 콜러

3
그러나 생성 된 MSIL을 실행하는 C # 컴파일러와 CLR은 서로 다릅니다. CLR이 C #으로 작성되지 않았 음을 확신합니다.
jocull

39

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 코드를 컴파일 할 수 있습니다 (그리고 증분 컴파일 모드는 훨씬 더 효율적입니다).


17
Go의 컴파일러는 작은 함수를 인라인합니다. 적은 수의 CPU를 대상으로 지정하면 속도가 느려지는지 잘 모르겠습니다. x86 용으로 컴파일하는 동안 gcc가 PPC 코드를 생성하지 않는다고 가정합니다.
Brad Fitzpatrick

@BradFitzpatrick은 오래된 의견을 부활시키는 것을 싫어하지만 더 적은 수의 플랫폼을 대상으로 컴파일러 개발자는 각 의견에 대해 더 많은 시간을 할애 할 수 있습니다.
지속성

중간 형식을 사용하면 훨씬 더 많은 아키텍처를 지원할 수있게되었으므로 이제는 각각의 새 아키텍처에 대해 새로운 백엔드 만 작성하면됩니다
phuclv

34

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

마지막으로, 빠른 속도를 목표로합니다. 단일 컴퓨터에서 큰 실행 파일을 빌드하는 데 최대 몇 초가 걸립니다. 이러한 언어 적 문제를 해결하기 위해 이러한 목표를 달성하기 위해서는 표현력이 있지만 가벼운 유형의 시스템; 동시성 및 가비지 콜렉션; 엄격한 의존성 사양; 등등.자주하는 질문

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

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


6
그건 사실이 아니야. 기호 테이블이 없으면 Go 소스 코드를 완전히 구문 분석 할 수 없습니다.

12
또한 가비지 수집이 컴파일 시간을 향상시키는 이유를 알지 못합니다. 그렇지 않습니다.
TheBlastOne

3
이들은 FAQ에서 인용 한 것입니다 : golang.org/doc/go_faq.html 그들이 목표를 달성하지 못했는지 (심볼 테이블) 논리가 잘못되었는지 (GC) 말할 수 없습니다.
Larry OBrien

5
@FUZxxl로 이동 golang.org/ref/spec#Primary_expressions 과는 두 시퀀스 [피연산자, 전화] 및 [변환]을 고려한다. 예 Go 소스 코드 : identifier1 (identifier2). 기호 테이블이 없으면이 예제가 통화인지 변환인지를 결정할 수 없습니다. | 모든 언어는 기호 테이블없이 어느 정도 구문 분석 될 수 있습니다. Go 소스 코드의 대부분이 기호 테이블없이 구문 분석 될 수 있지만, golang 스펙에 정의 된 모든 문법 요소를 인식 할 수는 없습니다.

3
@Atom 파서가 오류를보고하는 코드 조각이되지 않도록 열심히 노력합니다. 파서는 일반적으로 일관된 오류 메시지를보고하는 데 어려움을 겪습니다. 여기서는 aType변수 참조 인 것처럼 식에 대한 구문 분석 트리를 만들고 나중에 의미 분석 단계에서 의미있는 오류가 인쇄되지 않는 것을 알게됩니다.
Sam Harwell

26

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

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


22

컴파일러의 변환 효율성에 대한 좋은 테스트는 자체 컴파일입니다. 주어진 컴파일러가 자체 컴파일하는 데 얼마나 걸립니까? C ++의 경우 시간이 오래 걸립니다 (시간?). 이에 비해 Pascal / Modula-2 / Oberon 컴파일러는 하나 미만으로 자체 컴파일됩니다 최신 머신에서 초 [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의 경우 전혀 확실하지 않습니다).

어쨌든 언어 채택을 주도하는 플랫폼이며 다른 방식은 아닙니다.

참고 문헌 :

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf , 6 페이지 : "컴파일러가 약 3 초 안에 컴파일됩니다". 이 인용문은 25MHz의 클럭 주파수에서 실행되고 1MByte의 메인 메모리를 갖춘 저가의 Xilinx Spartan-3 FPGA 개발 보드를위한 것입니다. 이것으로부터 1GHz 이상의 클럭 주파수와 몇 GBytes의 주 메모리 (즉, Xilinx Spartan-3 FPGA 보드보다 몇 배나 더 강력한)에서 실행되는 최신 프로세서의 경우 1 초 미만으로 쉽게 추정 할 수 있습니다 . I / O 속도를 고려할 때도 마찬가지입니다. Oberon이 2-4MB의 주 메모리를 가진 25MHz NS32X32 프로세서에서 실행 된 1990 년에 이미 컴파일러는 몇 초 만에 자체 컴파일되었습니다. 실제로 기다리는 개념컴파일러가 컴파일주기를 마치기 전까지는 Oberon 프로그래머에게는 완전히 알려지지 않았습니다. 일반적인 프로그램의 경우 컴파일러가 방금 트리거 한 컴파일을 완료하기를 기다리는 것보다 컴파일 명령을 트리거 한 마우스 버튼에서 손가락을 제거하는 데 항상 더 많은 시간이 걸렸습니다. 거의 제로에 가까운 대기 시간으로 즉각적인 만족감이었습니다. 그리고 당시에 사용 가능한 최고의 컴파일러와 완전히 동일하지는 않지만 제작 된 코드의 품질은 대부분의 작업에 상당히 좋았으며 일반적으로 상당히 수용 가능했습니다.


1
Pascal / Modula-2 / Oberon / Oberon-2 컴파일러는 최신 머신에서 1 초 이내에 컴파일됩니다. [citation needed]
CoffeeandCode

1
인용 추가, 참조 [1] 참조.
Andreas

1
"... 원칙 ... Go (Google), Swift (Apple)와 같은 현대 언어로의 길 찾기"Swift가 어떻게 그 목록을 만들 었는지 잘 모르겠습니다 . Swift 컴파일러는 빙하 입니다. 최근 CocoaHeads Berlin 모임에서 누군가 중간 크기의 프레임 워크에 숫자를 제공하여 초당 16 LOC에 도달했습니다.
mpw December

13

Go는 빠르도록 설계되었으며 잘 보여줍니다.

  1. 종속성 관리 : 헤더 파일이 없으므로 직접 가져온 패키지 (가져 오는 것에 대해 걱정할 필요가 없음)를보아야하므로 선형 종속성이 있습니다.
  2. 문법 : 언어의 문법은 단순하여 쉽게 파싱됩니다. 기능의 수는 줄어들지 만 컴파일러 코드 자체는 빡빡합니다 (경로가 거의 없음).
  3. 과부하가 허용되지 않습니다. 심볼이 표시되고 어떤 방법을 참조하는지 알 수 있습니다.
  4. 각 패키지를 독립적으로 컴파일 할 수 있기 때문에 Go를 병렬로 컴파일하는 것은 간단합니다.

GO는 그러한 기능을 가진 유일한 언어는 아니지만 (모듈은 현대 언어의 표준 임), 잘 해냈습니다.


포인트 (4)는 전적으로 사실이 아닙니다. 서로 의존하는 모듈은 상호 모듈 인라이닝 및 항목을 허용하도록 종속성 순서대로 컴파일해야합니다.
fuz

1
@FUZxxl : 최적화 단계에만 관련이 있지만 백엔드 IR 생성까지 완벽한 병렬 처리를 수행 할 수 있습니다. 따라서 링크 단계에서 수행 될 수있는 교차 모듈 최적화 만 고려되며, 링크는 어쨌든 평행하지 않습니다. 물론, 작업을 복제하지 않으려는 경우 (구문 분석) "격자"방식으로 컴파일하는 것이 좋습니다. (1)과 (2)에만 의존합니다.
Matthieu M.

2
Makefile과 같은 기본 유틸리티를 사용하면 완벽하게 수행 할 수 있습니다.
fuz

12

Alan Donovan과 Brian Kernighan의 " The Go Programming Language " 책에서 인용 한 내용 :

Go 컴파일은 처음부터 빌드 할 때도 대부분의 다른 컴파일 된 언어보다 훨씬 빠릅니다. 컴파일러 속도에는 세 가지 주요 이유가 있습니다. 먼저, 모든 가져 오기는 각 소스 파일의 시작 부분에 명시 적으로 나열되어야하므로 컴파일러는 종속성을 판별하기 위해 전체 파일을 읽고 처리 할 필요가 없습니다. 둘째, 패키지의 종속성은 유향 비순환 그래프를 형성하며 사이클이 없기 때문에 패키지를 개별적으로 또는 병렬로 컴파일 할 수 있습니다. 마지막으로 컴파일 된 Go 패키지의 오브젝트 파일은 패키지 자체뿐만 아니라 종속성에 대한 내보내기 정보도 기록합니다. 패키지를 컴파일 할 때 컴파일러는 각 가져 오기에 대해 하나의 객체 파일을 읽어야하지만 이러한 파일을 넘어서는 안됩니다.


9

컴파일의 기본 아이디어는 실제로 매우 간단합니다. 재귀 강하 파서는 원칙적으로 I / O 바운드 속도로 실행될 수 있습니다. 코드 생성은 기본적으로 매우 간단한 프로세스입니다. 기호 테이블과 기본 유형 시스템은 많은 계산이 필요한 것이 아닙니다.

그러나 컴파일러 속도를 늦추는 것은 어렵지 않습니다.

다중 레벨 포함 지시문, 매크로 정의 및 조건부 컴파일 이있는 전 처리기 단계가있는 경우 ,이를로드하는 것은 어렵지 않습니다. (예를 들어, Windows 및 MFC 헤더 파일을 생각하고 있습니다.) 사전 컴파일 된 헤더가 필요한 이유입니다.

생성 된 코드를 최적화하는 측면에서 해당 단계에 추가 할 수있는 처리량에는 제한이 없습니다.


7

구문이 매우 쉬우므로 (분석하고 파싱하기 때문에) 간단하게 (내 자신의 말로)

예를 들어, 유형 상속이 없다는 것은 새로운 유형이 기본 유형에 의해 부과 된 규칙을 따르는 지 확인하기위한 문제가없는 분석을 의미합니다.

예를 들어이 코드 예제에서 : "인터페이스" 컴파일러는 해당 유형을 분석하는 동안 지정된 유형 이 지정된 인터페이스를 구현 하는지 확인하지 않습니다 . 사용 (및 사용 된 경우)까지만 점검이 수행됩니다.

다른 예에서, 컴파일러는 변수를 선언하고 사용하지 않는지 알려줍니다 (또는 반환 값을 보유하고 있고 그렇지 않은 경우)

다음은 컴파일되지 않습니다 :

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

이러한 종류의 시행과 원칙 은 결과 코드를 더 안전하게 만들어 주며, 컴파일러는 프로그래머가 할 수있는 추가적인 검증을 수행 할 필요가 없습니다.

이 모든 세부 사항은 언어를 구문 분석하기 쉽게 만들어 컴파일 속도가 빠릅니다.

다시 말하지만, 내 말로.


3

Go는 컴파일러 작성과 병렬로 설계되었으므로 가장 친한 친구였습니다. (IMO)


0
  • Go 는 모든 파일에 대해 종속성을 한 번 가져 오므로 가져 오기 시간이 프로젝트 크기에 따라 기하 급수적으로 증가하지 않습니다.
  • 언어학이 단순할수록 해석에 소요되는 시간이 줄어 듭니다.

또 뭐요?

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.