책 Go 성능 최적화 가이드이 4장에서는 컴파일 과정이 나온다. 실행파일이 나오기까지의 과정이 대충이라도 알고 싶어서 복잡해 보이지만 정리를 시작했다. 정리를 하다보니 책에 있는 내용과 더불어 없는 내용도 일부 추가했다. 모르는 내용이 많아 사실 내 옵시디언에서는 수많은 링크와 알아듣기 쉽도록 예제를 달아두었으나 여기서는 생략했다. 😅 내용이 너무 많다 🤮
Go 소스 코드를 구문 분석하여 토큰 (Token) 으로 분할.
크게 문법검사, 구조분석, 코드변환의 기반이 된다.
▸ token : 소스 코드를 구성하는 가장 작은 의미 단위
예시 :
토큰 | 유형(Token Type) | 설명 |
---|---|---|
func | 키워드(Keyword) | 함수 정의 시작을 나타냄 |
add | 식별자(Identifier) | 함수 이름 |
( | 구분자(Delimiter) | 매개변수 목록 시작 |
... | ... | ... |
코드의 논리적 관계를 표현하며 코드 흐름의 제어 구조를 명확히 한다.
이후 AST는 컴파일러의 후속 작업에서 사용되어 최적화 작업의 기반이 된다.
▸ AST : 컴파일러가 소스 코드를 처리할 때, 코드를 이해하고 분석하기 위해 소스 코드의 문법적 구조를 트리 형태로 표현.
func add(a int, b int) int {
return a + b
}
Function (add)
├── Parameters
│ ├── a : int
│ └── b : int
├── ReturnType : int
└── Body
└── Return
└── Add
├── Variable : a
└── Variable : b
😉 부가 작업
문법적 scope 내
🏃♂️ 최적화 관련 작업
🧠 메모리 관리 및 안전성
🔎 코드 분석 및 정적 검증
실행중
📌 이스케이프 분석(Escape Analysis)
컴파일러가 변수가 함수의 범위를 벗어나는지 여부를 판단하고, 이를 기반으로 스택 또는 힙에 적절히 메모리를 할당하는 과정이다.
AST에 대한 초기 최적화 후 트리는 SSA(Static Single Assignment) 형태로 변환된다. SSA는 변수의 값을 한 번만 할당하는 방식으로, 이후의 추가적인 최적화를 쉽게 만든다.
정적 단일 할당(Static Single Assignment, SSA)
코드적으로 어디서든 유효하게 적용될 소스코드의 논리적인 구조와 표현을 단순화 혹은 개선하는 작업을 수행한다.
하드웨어에 의존하지 않는 최적화 규칙 적용
불필요한 변수 제거 및 상수 전파
루프 전개
데드 코드 제거
메모리 안전성 보장
genssa 함수를 호출하여 SSA를 기반으로 기계어(ISA) 명령어로 변환한다. Go 컴파일러는 타겟 CPU 아키텍처(x86, ARM 등)와 운영체제에 적합한 명령어를 생성한다.
📌 genssa 함수
genssa
는 SSA 형태의 중간 표현을 기반으로 최종 실행 가능한 기계어 코드를 생성하는 Go 컴파일러의 중요한 단계이다. 하드웨어와 운영체제의 특성을 반영해 최적화된 기계어를 생성하며, 프로그램 실행 성능을 결정짓는 핵심 역할을 맡고있다.
하드웨어나 운영체제의 고유한 기능을 최대한 활용하여 성능을 높이기 위하여 타겟 하드웨어의 특성에 따라 추가 최적화를 수행한다. 타겟이 되는 CPU의 명령어 집합(Instruction Set Architecture, ISA)에 맞는 명령을 생성하는것이라고 보면 된다. 과정에서 SIMD(단일 명령 다중 데이터)와 같은 CPU 기능을 활용한다.
동적 브랜치 예측 최적화
컴파일러가 브랜치 정보를 CPU에 제공하고, CPU가 히스토리 기반 예측과 투기적 실행으로 브랜치 비용을 줄이는 과정임. Go 컴파일러는 타겟 CPU의 특성을 활용해 이러한 최적화를 지원함.
최종 '목적파일'은 일반적으로 파일 접미사가 .a인 Go archive라는 tar파일로 압축된다. 각 패키지에 대한 아카이브파일은 Go 링커나 다른 링커에서 단일 실행파일로 결합하는 데 사용 가능하다. 해당 아카이브 파일을 일반적으로 바이너리 파일 (binary file)이라고 한다.
목적파일에는 아래의 정보를 포함한다.
📌 목적 파일은 바이너리 파일의 한 종류이다.
1. 목적 파일(Object File):
- 컴파일된 소스 코드의 중간 산출물. 링킹 과정을 거쳐야 실행 가능.
- 일반적으로
.o
,.obj
확장자를 가짐.- 특징:
- 완전한 실행 파일이 아님.
- 다른 목적 파일이나 라이브러리와 결합해 실행 파일 생성.
- 바이너리 파일(Binary File):
- 기계가 이해할 수 있는 이진 데이터로 구성된 파일.
- 모든 목적 파일이 바이너리 형식을 가짐.
- 실행 파일도 바이너리 파일에 포함됨.
- 예
- 목적 파일(.o, .obj)
- 실행 파일(Windows: .exe, Linux: ELF)
- 데이터 파일(이미지, 오디오 등)
목적 파일은 이미 생성된 상태(각 패키지나 모듈이 컴파일을 거쳐 독립적으로 생성된 상태)에서 Go 런타임 및 라이브러리를 포함하여 최종 실행 파일(Binary File)을 생성한다.
📌 Go 언어는 컴파일러와 링커가 긴밀히 통합되어 동작한다. Go의
go build
명령은 이 두 단계를 자동으로 실행하여 컴파일과 링킹이 하나의 흐름으로 작동하기때문에 대부분의 개발자는 명시적으로 링킹을 의식하지 않아도 된다. 그래서 컴파일러 중심으로 설명하다 보면 링킹이 별도로 강조되지 않을 수 있다.
- 컴파일: 소스 코드를 목적 파일로 변환하는 단계.
- 링킹: 목적 파일들을 결합하여 실행 가능한 바이너리를 생성하는 단계.
혹시 틀린 내용을 찾으셨다면 지적을 부탁드립니다. 🙏