내 리눅스 컴퓨터에서 기본 실행 파일을 생성하는 hello world Go 프로그램을 준수했습니다. 하지만 간단한 Hello world Go 프로그램의 크기를보고 놀랐습니다. 1.9MB였습니다!
Go에서 이러한 간단한 프로그램의 실행 파일이 그렇게 큰 이유는 무엇입니까?
내 리눅스 컴퓨터에서 기본 실행 파일을 생성하는 hello world Go 프로그램을 준수했습니다. 하지만 간단한 Hello world Go 프로그램의 크기를보고 놀랐습니다. 1.9MB였습니다!
Go에서 이러한 간단한 프로그램의 실행 파일이 그렇게 큰 이유는 무엇입니까?
dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true
약 26MB의 바이너리 파일 을 생성합니다!
답변:
이 정확한 질문은 공식 FAQ에 나와 있습니다. 내 사소한 프로그램이 왜 그렇게 큰 바이너리입니까?
답변 인용 :
GC의 툴 체인 (의 링커
5l
,6l
및8l
) 정적 링크 할. 따라서 모든 Go 바이너리에는 동적 유형 검사, 리플렉션 및 패닉 타임 스택 추적을 지원하는 데 필요한 런타임 유형 정보와 함께 Go 런타임이 포함됩니다.Linux에서 gcc를 사용하여 정적으로 컴파일 및 링크 된 간단한 C "hello, world"프로그램은
printf
. 사용하는 동등한 Go 프로그램fmt.Printf
은 약 1.9MB이지만 더 강력한 런타임 지원 및 유형 정보를 포함합니다.
따라서 Hello World의 기본 실행 파일은 가비지 수집, 리플렉션 및 기타 많은 기능을 제공하는 런타임이 포함되어 있기 때문에 1.9MB입니다 (프로그램에서 실제로 사용하지는 않지만 거기에 있음). 그리고 텍스트 fmt
를 인쇄하는 데 사용한 패키지 구현 "Hello World"
(및 해당 종속성).
이제 다음을 시도 fmt.Println("Hello World! Again")
하십시오. 프로그램에 다른 줄을 추가 하고 다시 컴파일하십시오. 결과는 2x 1.9MB가 아니지만 여전히 1.9MB입니다! 예, 사용 된 모든 라이브러리 ( fmt
및 해당 종속성) 및 런타임이 이미 실행 파일에 추가되었으므로 방금 추가 한 두 번째 텍스트를 인쇄하기 위해 몇 바이트 만 더 추가됩니다.
다음 프로그램을 고려하십시오.
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
Linux AMD64 시스템 (Go 1.9)에서 다음과 같이 빌드하면 :
$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
크기가 약 2MB 인 바이너리를 얻습니다.
그 이유는 (다른 답변에서 설명했듯이) 우리가 상당히 큰 "fmt"패키지를 사용하고 있지만 바이너리도 제거되지 않았으며 이는 심볼 테이블이 여전히 존재한다는 것을 의미합니다. 대신 컴파일러에게 바이너리를 제거하도록 지시하면 훨씬 작아집니다.
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
그러나 다음과 같이 fmt.Println 대신 내장 함수 print를 사용하도록 프로그램을 다시 작성하면 :
package main
func main() {
print("Hello World!\n")
}
그런 다음 컴파일하십시오.
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld
우리는 더 작은 바이너리로 끝납니다. 이것은 UPX 패킹과 같은 트릭을 사용하지 않고도 얻을 수있는만큼 작기 때문에 Go 런타임의 오버 헤드는 대략 700Kb입니다.
바이너리 크기 문제는 golang / go 프로젝트 의 문제 6853 으로 추적됩니다 .
예를 들어, a26c01a (Go 1.4 용)를 커밋하여 hello world를 70kB로 잘라냅니다 .
그 이름을 기호 테이블에 쓰지 않기 때문입니다.
1.5 용 컴파일러, 어셈블러, 링커 및 런타임이 모두 Go에 포함된다는 점을 고려하면 추가 최적화를 기대할 수 있습니다.
2016 Go 1.7 업데이트 : 최적화되었습니다. " Smaller Go 1.7 바이너리 "를 참조하십시오 .
하지만 오늘날 (2019 년 4 월) 가장 많이 차지하는 것은 runtime.pclntab
. Raphael 'kena'Poss의 " Go 실행 파일이 왜 그렇게 큰가요? D3를 사용하는 Go 실행 파일의 크기 시각화
"를 참조하십시오 .
문서화가 잘되어 있지는 않지만 Go 소스 코드의이 주석은 그 목적을 암시합니다.
// A LineTable is a data structure mapping program counters to line numbers.
이 데이터 구조의 목적은 Go 런타임 시스템이 충돌시 또는
runtime.GetStack
API 를 통한 내부 요청시 설명 스택 추적을 생성 할 수 있도록하는 것 입니다.그래서 유용 해 보입니다. 그런데 왜 그렇게 큽니까?
앞서 링크 된 소스 파일에 숨겨진 URL https://golang.org/s/go12symtab 은 Go 1.0과 1.2 사이에 발생한 일을 설명하는 문서로 리디렉션됩니다. 의역하기 :
1.2 이전에는 Go 링커가 압축 된 라인 테이블을 내 보냈고 프로그램은 런타임에 초기화시 압축을 풀었습니다.
Go 1.2에서는 실행 파일의 라인 테이블을 추가 압축 해제 단계없이 런타임에 직접 사용하기에 적합한 최종 형식으로 미리 확장하기로 결정했습니다.
즉, Go 팀은 초기화 시간을 절약하기 위해 실행 파일을 더 크게 만들기로 결정했습니다.
또한 데이터 구조를 살펴보면 컴파일 된 바이너리의 전체 크기가 각 함수의 크기와 함께 프로그램의 함수 수에서 매우 선형적인 것으로 보입니다.