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.