[Xcode / Swift] 빌드 시스템에 대한 이해 / 증분 빌드 / 병렬 처리 / Eager Linking / Incremental

2024. 10. 29. 19:12🍏/Xcode

이 글을 쓰게 된 이유.. 웅장해진다...

 

[Architecture] Modular Architecture VS Clean Architecture

어느날 문득 Modular Architecture와 Clean Architecture에 대한 생각을 하게 되었습니다. 모듈화나 클린이나 결국엔 개념적으로 레이어를 나누고 개발자들의 상황에 맞춰 선택적으로 사용할텐데, 둘은 어

chanhhh.tistory.com

Modular ArchitectureClean Architecture는 의존성 관리 방식에서 차이를 보입니다.

  • Modular Architecture에서는 모듈 간 의존성이 명시적으로 설정됩니다. 즉, 하나의 모듈이 필요한 경우 다른 모듈에 의존성을 가지며, 이를 통해 기능별로 독립된 모듈들이 서로 필요한 부분에 연결됩니다. 
  • Clean Architecture에서는 각 레이어가 상위 레이어에만 의존하게 하여, 낮은 레벨의 레이어가 높은 레벨의 레이어에 종속되지 않도록 설계됩니다. 

요약하면, Modular Architecture는 기능 단위의 독립적 모듈 간 의존성 관리, Clean Architecture는 레이어 간 명확한 의존성 규칙을 통해 코드의 결합도를 낮추는 방식이라고 볼 수 있습니다.

이때 두 아키텍처에서 어떤 기능을 수정했을때 증분 빌드는 어떻게 작동할 것인가 ? 에 대한 궁금증이 들어서 이 글을 쓰게 되었습니다. 


들어가기 앞서..

두 아키텍처의 증분 빌드에 대한 이해를 들어가기 앞서 우선 빌드가 뭔지 알아보자.
아래는 Xcode 빌드의 병렬 처리에 관한 WWDC22의 컨텐츠입니다. 빌드에 대한 내용을 자세하게 담고있습니다.

 

Xcode 빌드에서의 병렬 처리에 대한 오해 해소 - WWDC22 - 비디오 - Apple Developer

Xcode 빌드 시스템이 빌드에서 최대 병렬 처리를 추출하는 방법을 알아보세요. 프로젝트를 구조화하여 빌드 효율을 개선하는 방법을 살펴보고, Xcode에서 타겟의 빌드 단계 간의 관계를 해결하기

developer.apple.com

01

Xcode 빌드 시스템의 이해

컴파일 과정

Xcode의 컴파일러는 Clang과 Swift를 사용하여 소스 코드를 객체 파일로 변환/생성합니다. 이 객체 파일들은 프로그램의 다양한 구성 요소들을 나타내며, 실행 파일을 생성하는 데 필수적인 요소입니다.

링크 과정

생성된 객체 파일들은 링커에 의해 결합됩니다. 링커는 이 객체 파일들을 하나의 실행 파일로 통합하고, 필요한 외부 라이브러리 참조를 추가하여 프로그램이 외부 코드와 상호작용할 수 있도록 합니다.

종속성 관리

빌드 시스템의 핵심은 종속성 관리입니다. 파일 콘텐츠 자체보다는 작업 간의 종속성이 중요합니다. 특정 작업이 다른 작업의 입력을 생성할 때, 그 작업이 완료되기 전까지는 다음 작업을 시작할 수 없습니다. 이를 통해 모든 작업이 올바른 순서로 실행될 수 있도록 합니다.

작업의 실행

차단이 해제된 작업(Unblock) > 다운스트림
차단하는 작업은(Block)  > 업스트림.

종속성이 없는 작업은 먼저 실행되며, 이러한 작업들이 완료된 후 다운스트림 작업이 진행됩니다. 이 과정은 모든 계획된 작업이 완료될 때까지 이 프로세스를 따릅니다. 초기 빌드가 아닌 이후 빌드에서는, 출력이 최신 상태인 경우 입력이 변경되지 않은 작업은 빌드 시스템이 건너뛸 수 있습니다. 입력이 변경된 경우 해당 작업과 관련된 다운스트림 작업도 재실행됩니다.

위 사진을 보면 프레임워크와 앱 익스텐션은 종속적이지 않기 때문에, 병렬로 작업이 실행되는 것을 확인 할 수 있습니다.

 

증분 빌드(Incremental build)?

프로젝트 전체를 처음부터 빌드하는 것이 아니라 변경된 부분만을 대상으로 빌드를 수행하는 방법입니다. 위와 같은 빌드의 다른 모든 작업을 모두 건너 뛸 수 있다면 프로젝트의 처리 시간이 매우 줄어듭니다. 반복 작업시 배우 빨라지며 이를 증분 빌드라고 합니다. 즉, 최근 빌드 이후 수정된 소스 코드나 리소스 파일들만 새로 컴파일하거나 링크하여 전체 빌드 시간을 단축시키는 효율적인 프로세스입니다.

빌드 요약

  1. 컴파일러가 O 파일 생성, 링커가 개체 파일 소비
  2. 작업 간의 종속성을 관리하여 파일들을 올바른 순서로 처리
  3. 작업 실행. 적절한 업스트림과 다운스트림으로 종속성에 맞춰 작업 실행
  4. 재빌드(증분빌드) 변경된 부분만 대상으로 빌드 수행.

위의 내용을 토대로 빌드에 대해 이해하게 되었습니다.
근데 이제 제가 궁금해 했던 점을 알아봐야겠죠 ? 우선 Clean Architecture의 증분 빌드 부터 생각해보 겠습니다.

Clean Architecture의 증분 빌드

클린아키텍쳐의 증분빌드는 레이어로 나뉜 소스코드일 뿐이니 01번과 같이 진행이 됨을 알 수가 있습니다. 
이때 병렬화를 증가시키는 방법에 대해서 영상에서 이야기하고 있습니다. (10:34)

02

FUSE_BUILD_SCRIPT_PHASES를 사용하면 된다고 합니다.

03

ENABLE_USER_SCRIPT_SANDBOXING으로 커스텀하게 병렬화를 세세하게 조정 할 수 있음을 알려줍니다.

 

Modular Architecture의 증분 빌드

Modular Architecture는 Target과 Module(xcodeproj)로 이루어진 모듈화 아키텍쳐라서 전체 앱의 의존성이 하위 모듈에 걸려있고 해당 모듈이 기능이 정의된 Target에 의존성을 걸고 있으니 앱 전체 빌드에 대한 빌드의 이점을 갖기 어려울 것이라 생각했습니다.

가장 궁금했던게 이 영상에서 해소가 됐습니다. 

04

위 사진은 타겟을 의존성으로 갖는 시스템에 대해서 빌드 플로우를 보여줍니다.
보이는 그대로 빌드 플로우를 갖게되면 사실 거대한 Modular Architecture에서 빌드를 하게 되었을 때, 엄청나게 빌드 시간이 들겁니다. 여기서 프레임워크 내부에서 증분빌드와 xcode의 빌드의 병렬성 덕분에 생각보다 적은 시간으로 빌드를 할 수 있게 해줍니다. 여기에서 Swift Driver가 빌드를 도와줌으로써 타겟간의 병렬성과 증분빌드에 이점을 극대화 시켜줍니다. 


Swift 타겟 종속성은 종속 항목이 public interface를 캡쳐하는 바이너리 모듈 파일을 제공하도록 함으로써 해결됩니다.
Xcode 14부터는 Swift로 작성된 완전히 새로운 Swift-Driver 덕분에 빌드 시스템과 컴파일러가 완전히 통합되었습니다.
Xcode 빌드시스템은 코드 컴파일을 위해 수행해야 하는 모든 작업의 central scheduler 관리자 역할을 합니다.
이 중앙 계획 매커니즘을 통해 Xcode는 세밀한 스케줄링 결정을 내릴 수 있고 프로젝트를 빌드할 때 사용 가능한 CPU 초과 구독과 시스템 성능 저하 없이 리소스만큼만 사용하도록 보장해 줍니다.

 

위의 영상을 보면 한번에 이해하기 쉬우실 겁니다. 

Xcode 14 및 Swift 5.7 부터는 새롭게 타겟 모듈의 구성이 모든 소스 파일에서 직접 별도의 emit-module 작업에서 수행됩니다. 이는 타겟 종속성이 모듈 방출 작업이 완료되는 즉시 다른 컴파일러 작업을 기다리지 않고 컴파일을 시작할 수 있음을 의미합니다. 프로젝트로 확장해보면 전반적으로 비슷한 양의 작업을 수행하고 있지만 빌드 시스템은 컴퓨터의 리소스를 더 효율적으로 사용할 수 있고 빌드를 훨씬 더 빠르게 완료할 수 있습니다.

Eager linking ?

Eager linking 01

일반적으로 두 타겟의 종속성 그래프는 위 사진과 같습니다.
종속 타겟을 링킹하려면 타겟 자체의 컴파일 출력과 함께 자체 종속성들이 링크된 product가 필요합니다.

Eager linking 02

Eager linking을 사용하면 이 종속성이 깨지면서 종속 타겟이 더 일찍 링킹 될 수 있도록 합니다.
디펜던시가 링크된 product에 의존하는 대신, 이전 빌드 프로세스에서 emit-module 작업에 의해 생성된 텍스트 기반 dynamic library stub에 의존하게 됩니다. 이 stub에는 종속성이 사용할 수 있도록 링크된 product의 심볼 목록이 포함되어 있습니다.

Eager linking 03

Xcode build setting에서 이 최적화를 활성화 할 수 있습니다.
Eager linking은 디펜던시 항목에 의해 동적으로 링킹된 모든 Swift-only 타겟에 적용됩니다.

결론적으로

Xcode 빌드 시스템은 정교한 스케쥴링 엔진으로 빌드 단계를 병렬로 실행하여 가능한 많은 병렬성을 추출합니다.
스크립트 샌드박싱과 같은 기능을 사용한다면 빌드가 최대한 병렬 되고도 신뢰할 수 있도록 보장할 수 있습니다.
Xcode와 Swift의 통합으로 인해 프로젝트의 구조, 모듈화, 타겟 product 사이의 종속성으로 구성된 전체적 그래프 모양 및 그 안에 있는 빌드 단계의 수와 복잡성, 기기의 자원까지 Xcode가 고려하여 빌드를 병렬화하고 속도에 대해 기여를 할 수 있게 되었습니다.


느낀점

Xcode 빌드에서의 병렬 처리에 대한 오해 해소가 이 영상의 주제였는데, 저의 기존 생각과는 다른 많은 부분들이 있었습니다. 병렬 처리와 증분 빌드의 원리를 이해함으로써, 이전에는 간과했던 여러 세부적인 최적화 방법을 알게 되었습니다. 특히, Swift-Driver의 역할과 타겟 종속성 해결 방법에 대한 설명은 Xcode 내에서의 빌드 프로세스를 효율적으로 관리하는 데 큰 도움이 될 것 같습니다. 

이를 통해 배운 지식을 실제 프로젝트에 어떻게 적용할 수 있을지에 대해 구체적인 아이디어를 얻었으며, 향후 이전에 진행했던 프로젝트들의 빌드 Assistant를 토대로 실 상황에서 어떻게 쓰이고 어떤식으로 유의미한 결과를 낼 수 있는지에 대해서 알아보려고 합니다. 

정말 제목 그대로 제가 알고 있던 오해를 해소하게 되어 뜻깊은 시간이었으며, 이 글을 통해 새롭게 배운 내용을 정리하면서 이해가 되어서 글을 쓰는 내내 즐거운 시간이였습니다. 긴 글 읽어주셔서 감사합니다. ☺