정글에서 온 개발자
Go가 빌드 시간 문제를 해결한 방법 본문
Go 탄생신화에 따르면 Go는 45분의 빌드 타임 동안에 잉태되었다고 한다.
C, C++의 방식
/* 긴 길이의 저작권과 라이센스 알림 */
#ifndef _SYS_STAT_H_
#define _SYS_STAT_H_
/* 타입과 다른 정의들 */
#endif
c는 이렇게 guard 하는 ANSI C 방식이 널리 퍼져있었다. 이 방법은 의존성 파일을 중복 임포트하지 않기 위한 것이지만, 이 guard를 읽기 위해 여러번의 의미 없는 i/o가 일어났다.
아티클에서는 Unix의 ps 명령어가 #include<sys/stat.h> 를 37번 사용하는 것을 발견했고, 이는 의미없는 36번의 파일 I/O가 일어나는 것을 의미한다.
이 문제 해결을 위해 Plan 9이라는 라이브러리의 디자이너는 다른 방법을 사용했다. 모든 헤더파일에서 #include 사용을 금지하고 최상위 C파일에서만 사용할 수 있게 한 것이다. 정확히 한번만, 순서대로 임포트하는 것이 중요했지만 문서화 덕분에 다행히 잘 작동했다.
그러나 이 접근법이 라이브러리의 계층적으로 적용되었기 때문에 문제를 해결하지는 못했다. 게다가 헤더에다가 코드를 넣는 경우도 흔했다. 이는 종속성만 읽으려고 헤더를 읽더라도 코드까지 반복적으로 읽게 되는 문제를 야기했다.
Go의 방법
1. Go 는 import문을 통해 종속성이 문법적이고 의미적으로 정의될 수 있다.(사실 이 부분은 파이썬과 node.js에서도 하고 있는 부분이다.) 이렇게 의존성이 "계산 가능"해진다.
2. 여기에 더해 사용하지 않는 패키지가 import되면, 컴파일 "에러" (경고 수준이 아님) 를 발생시킨다. 이 때문에 VSC 포매터가 import되지 않은 패키지를 자동으로 지워버려서 성가실 때도 있긴 하다.
3. A->B->C 의 import 관계가 있다고 할 때 (A는 C를 직접 임포트 하지 않음;이행적) 각 의존성 파일을 한번씩만 읽게 하는 기법이 있다.
의존 관계를 분석해 C 컴파일 -> B컴파일 -> A 컴파일 순으로 컴파일을 한다.
그런데 A 컴파일을 할 때 B의 소스코드가 아닌 '오브젝트 파일'을 읽는데, 이 B 의오브젝트 파일에 B의 public 인터페이스에 필요한 C의 정보가 모두 들어가 있다. 이렇게 하면 import문 하나당 하나의 파일만 읽게 된다. 즉, A 컴파일시 B의 의존성을 따라가며 C의 파일까지 다시 읽을 필요가 없게 된다.
이 방법은 위의 Plan 9 라이브러리와 유사하지만 더 좋다. Plan 9은 의존성 해소를 위해 (지저분한 소스코드가 포함됐을 수 있는)헤더 파일을 읽게 되지만, Go의 방법은 좀 더 필요한 것만 '추출된' 정보를 읽기 때문이다.
여기서 오브젝트 파일에 export data를 위치시키는 점도 중요한데, 다른 언어는 이런 export data를 다른 파일에 써서 파일을 두배로 읽게 만들기 때문이다. export data는 오브젝트 파일의 가장 위에 위치해서, 뒤의 쓸데없는 내용이 있더라도 data를 읽을 때는 필요한 만큼만 읽을 수 있다.
4.마지막으로 순환 의존성이 없는걸 보증한다.
순환 의존성은 증분 빌드에 악영향을 준다.
효과
구글의 대규모 프로그램 컴파일 테스트 결과 c++에 비해 50배의 효과를 봤다고 한다.
참고
'TIL' 카테고리의 다른 글
Go 웹 API swagger로 문서화 패키지 고르기 (0) | 2025.01.20 |
---|---|
1/19 TIL 내가 몰랐던 Go 웹 서버 개발시 코드 패턴 (0) | 2025.01.19 |
Go가 탄생한 이유 (0) | 2025.01.19 |
1/15 TIL 단위 테스트 안티패턴 (0) | 2025.01.16 |
1/14 TIL 데이터베이스 테스트 (0) | 2025.01.15 |