대규모 소프트웨어 프로젝트는 일반적으로 비교적 독립적으로 컴파일 할 수있는 많은 컴파일 단위로 구성되므로 컴파일러를 여러 번 병렬로 호출하여 컴파일을 매우 세분화합니다. 이것은 OS 프로세스 수준에서 발생하며 컴파일러가 아닌 빌드 시스템에 의해 조정됩니다. 나는 이것이 당신이 요구 한 것이 아니라는 것을 알고 있지만 대부분의 컴파일러에서 병렬화에 가장 가깝습니다.
왜 그런 겁니까? 컴파일러가 수행하는 많은 작업이 병렬화에 쉽게 도움이되지는 않습니다.
- 입력을 여러 청크로 나누고 독립적으로 lex 할 수는 없습니다. 단순성을 위해 lexme 경계에서 분할하고 싶기 때문에 (lexme 중간에서 스레드가 시작되지 않음) lexme 경계를 결정하려면 많은 컨텍스트가 필요합니다. 예를 들어, 파일 중간에서 점프 할 때 문자열 리터럴로 점프하지 않았는지 확인해야합니다. 그러나 이것을 확인하기 위해서는 기본적으로 이전에 등장했던 모든 캐릭터를 살펴 봐야합니다. 게다가, 렉싱은 현대 언어의 컴파일러에서 병목 현상이 거의 없습니다.
- 구문 분석은 병렬화하기가 더 어렵습니다. 어휘를 위해 입력 텍스트를 분할하는 모든 문제는 구문 분석을 위해 토큰을 분할하는 데 훨씬 더 적용됩니다. 이 문제를 해결할 수있는 방법이있을 수도 있지만, 약간의 이점을 위해 불균형 적으로 복잡 할 것입니다. 파싱도 가장 큰 병목 현상이 아닙니다.
- 구문 분석 한 후에는 일반적으로 이름 확인을 수행해야하지만 이로 인해 서로 밀접한 관계가 형성됩니다. 여기에서 메소드 호출을 해결하려면 먼저이 모듈에서 가져 오기를 해결해야하지만 다른 컴파일 단위 에서 이름을 해석해야 합니다. 언어에 해당하는 경우 유형 유추와 동일합니다.
이 후 약간 쉬워집니다. 원칙적으로 형식 검사 및 최적화 및 코드 생성이 기능 단위로 병렬화 될 수 있습니다. 컴파일러 가이 작업을 수행하는 경우 거의 알지 못합니다.이 작업을 동시에 수행하는 것이 매우 어려울 수 있습니다. 또한 대부분의 대규모 소프트웨어 프로젝트에는 너무 많은 컴파일 단위가 포함되어있어 "여러 컴파일러를 병렬로 실행"접근 방식으로 모든 코어 (및 경우에 따라 전체 서버 팜)를 유지하기에 충분합니다. 또한 대규모 컴파일 작업에서 디스크 I / O는 실제 컴파일 작업만큼 병목 현상이 발생할 수 있습니다.
코드 생성 및 최적화 작업을 병렬화하는 컴파일러를 알고 있습니다. Rust 컴파일러는 백엔드 작업 (실제로는 "중간 엔드"로 간주되는 코드 최적화를 포함하는 LLVM)을 여러 스레드로 분할 할 수 있습니다. 이것을 "코드 생성 단위"라고합니다. 위에서 설명한 다른 병렬화 가능성과 달리, 다음과 같은 이유로 경제적입니다.
- 이 언어에는 컴파일 단위가 크므로 (C 또는 Java와 비교) 코어가있는 것보다 적은 컴파일 단위가있을 수 있습니다.
- 병렬화되는 부분은 대개 컴파일 시간의 대부분을 차지합니다.
- 백엔드 작업은 대부분 당혹스럽게 병렬입니다. 각 기능을 독립적으로 기계 코드로 최적화하고 번역하기 만하면됩니다. 물론 절차 간 최적화가 있으며 코드 단위는이를 방해하여 성능에 영향을 주지만 의미 상 문제는 없습니다.