본문 바로가기

WWDC

[WWDC18] Building Faster in Xcode

https://developer.apple.com/videos/play/wwdc2018/408/

 

Building Faster in Xcode - WWDC18 - Videos - Apple Developer

Build your apps faster in Xcode 10. Learn how to structure your projects and tweak your code to take full advantage of all processor...

developer.apple.com

 

목차

  1. 빌드란?
  2. 빌드 종류(클린 빌드, 증분 빌드)
  3. 클린 빌드 빠르게 하기
  4. 증분 빌드 빠르게 하기
  5. 마무리

 

빌드란?

  • 전처리기: 컴파일 전에 소스코드를 "준비"해주는 역할(ex. #Debug 같은 조건문 처리)
  • 컴파일러: 고수준언어(Swift: let a = 3) -> 저수준언어(어셈블리어: MOV R0, #3(레지스터 R0에 3을 넣는다))  변환
  • 어셈블러: 저수준 언어 -> 기계어(CPU가 이해하는 0과 1 이진수 표현) 변환
  • 링커: 여러 개의 컴파일된 코드(오브젝트 파일)를 하나의 실행 파일(.exe, .out 등)로 합쳐줌
  • 로더: 실행파일을 디스크에서 읽어서 메모리에 배치하고, CPU를 코드 실행 시작 위치(main 함수 같은 곳)에 점프시켜 프로그램을 실행함

간단하게 정리해서, 빌드란 소스 코드를 기계어로 변환시켜 실행파일로 만드는 과정입니다.

더보기

🤔
TMI: 빌드 과정을 소스 코드 전체 한번에 변환하고 실행파일을 Run하면 컴파일 언어, 코드 한줄한줄 변환하고 런타임에 바로 적용해서 실행하면 인터프리터 언어, 그리고 두개가 섞여져있는 하이브리드 언어입니다.
+ (갑자기 생각난건데 동료 웹개발자가 자바스크립트에서 값 찍어봐야한다고 했는데 Run중인데 그냥 print문 추가하고 재실행도 안했는데도 추가한 코드 바로 확인할 수 있었던 게 생각남. 자바스크립트가 인터프리터 언어라서 가능했던 것..!)

 

빌드 종류와 각 빌드 빠르게 하기

  • Build - 클린 빌드 (모든 빌드 산출물(캐시, 중간 결과물 등)을 삭제하고 처음부터 다시 빌드)
    • 빌드 병렬화하기 - 모듈화 구성
    • 빌드 시간 측정하기 - Xcode에서 빌드 타임라인 볼 수 있음의 내용
    • 복잡한 표현식 다루기 - 클로저에서 반환값 xcode가 추론하게 하지 말고 명시해주기, 숫자 표현식이나 계산식 한줄로 길게 쓰지 않기(ex. 삼항연산자안에 계산로직 안에 삼항연산자안에 계산로직~~)
  • Rebuild(인크리멘탈 빌드) - 증분 빌드 (변경된 파일만 다시 컴파일하고, 나머지는 재사용)
    • script inputs, ouputs 선언하기
    • 스위프트 의존성 이해하기
    • Object-C/Swift 인터페이스 제한하기: (옵젝씨 사용안해서 이건 생략..!)

 

Build - 클린 빌드 빠르게 하기

Parallelizing your build process (빌드 병렬화하기)

Game 프로젝트에 위와 같은 의존성을 보여주는 그래프가 있습니다.

위의 Game 프로젝트를 빌드하려고 하면 타임라인은 아래와 같이 순차적으로 이루어집니다.

그러나, 이렇게 직렬로 각 모듈을 순차적으로 빌드하는 것은 하드웨어 활용의 낭비일 수 있습니다. (특히 iMac Pro 같은 멀티코어 머신인 경우에)

그래서 하드 웨어를 더 잘 사용하기 위해 모듈 구조를 병렬 빌드 가능하도록 하는 것이 좋습니다.

WWDC에서 구조를 바꿈으로서 병렬 처리되는 예시로 보여준 것은 다음과 같습니다.

테스트 모듈이 있어서 각 모듈(Game, Shaders, Utilities)에서 테스트 모듈에 의존성을 가지고 있었는데 테스트를 Game, Shaders, Utilities 모듈에 각각 테스트 모듈을 만들고 연결하여 병렬 처리되도록 함.

 

Shaders는 GPU를 사용하고 Utilities는 CPU를 사용합니다. 그래서 종속성을 확인해보니 Utilities는 Shaders와 공통으로 사용하는 정보를 생성하고 있습니다. 이외에 다른 전체적인 코드의 Utilities는 필요없습니다.

이럴 경우 Code Gen이라는 중간 모듈을 따로 만들면 Utilities는 다른 타겟이 빌드하고 있을 때 기다릴 필요 없이 자신의 작업을 병렬로 수행할 수 있습니다.

때때로 코드를 수정할 때 삭제해야할 것을 잊을 때가 있습니다. 잊힌 의존성을 제거하는 것으로도 빌드를 효율화 할 수 있습니다.

 

Measuring your build time (빌드 시간 측정하기)

빌드 되는데에 각 작업에 소요되는 시간을 볼 수 있습니다.

 

Dealing with complex expressions in swift (복잡한 표현식 다루기 )

// 1.Xcode가 추론하기 어려우므로 명확하게 Double이라고 타입 적어주기 
struct ContrivedExample {
  var bigNumber: Double = [4, 3, 2].reduce(1) {
    soFar, next in
    pow(next, soFar)
  }
}

// 2.복잡한 클로저 타입 제공하기
func sumNonOptional(i: Int?, j: Int?, k: Int) -> Int? {
  return [i, j, k].reduce(0) {
    (soFar: Int?, next: Int?) -> Int? in // <- 이렇게 타입 제공하여 추론 안되는 것을 방지
    soFar != nil && next != nil ? soFar! + next! : 
      (soFar != nil ? soFar! : (next != nil ? next! : nil))
  }
}

// 2번 클로저안이 너무 복잡해서 타입 적어주는 것보다 더 좋은 방법
func sumNonOptional(i: Int?, j: Int?, k: Int) -> Int? {
  return [i, j, k].reduce(0) {
    soFar, next in
    
    if let soFar = soFar {
      if let next = next { return soFar + next }
      return soFar
    } else {
      return next
    }
  }
  
// 3. AnyObject 사용 대신 구체 타입 명시
/* 컴파일러는 어떤 메서드를 호출할지 모르기 때문에 프로젝트 전체에서 가능한 모든 구현과
가져온 프레임워크를 검색하여 이것이 사용될 것이라고 가정해야 합니다.
이렇게 하는 이유는 일치하는 것이 없으면 오류를 표시해야 하기 때문입니다.
이 작업은 같은 파일에서 수행할 수도 있고 다른 파일에서 수행할 수도 있지만
중요한 점은 이 대리자 속성을 Any Object 대신 우리 프로토콜을 사용하도록 변경하면
컴파일러가 어떤 메서드를 호출하는지 정확히 안다는 것 입니다. */

weak var delegate: AnyObject? // <- AnyObject말고 MyOperationDelegate으로 구체 타입 작성해!
func reportSuccess() {
  delegate?.myOperationDidSucceed(self)
}

protocol MyOperationDelegate: class {
  func myOperationDidSucceed(_ operation: MyOperation)
}

 

 

Rebuild(인크리멘탈 빌드) 빠르게 하기

Declaring script inputs and outputs

빌드 과정중에 작성한 쉘 스크립트를 같이 실행할 수 있도록 Run Script에 적어주곤 합니다. 그리고 그 아래에 input Files란 칸이 있습니다. 

  • Input File : Run Script가 읽는 파일
  • Output File : Run Script가 생성하는 파일

input Files, output Files는 적혀져있는 파일이 변경되면 Run Script를 다시 실행하는 요소중에 하나라고 합니다. 즉, 변경이 없다면 Run Script는 다시 실행이 되지 않아 증분 빌드 성공인 것입니다!

 

Understanding Dependencies in Swift (xcode는 파일 단위로 빌드가 된다.)

  • Swift는 소스 파일 단위로 컴파일
  • 파일 간 참조를 추적
  • 파일 안에 다른 타입을 추가하면 컴파일러는 해당 파일 간 참조를 추적하여 전체 파일을 재컴파일함(보수적 재빌드)

Point 객체에 init 초기자 추가하면 Point 파일 + Point를 의존하고있는 오른쪽 파일도 재컴파일됨

만약 초기자 내부만 변경된다면? -> 똑똑한 컴파일러는 Point 파일만 재컴파일 시킴

Point파일에 PathSegment 객체를 추가시키면 Point 파일 + Point 객체에 의존하고 있는 파일을 모두 재컴파일됨.(보수적 재빌드)

 

 

마무리

  • 빌드 : 고수준언어(Swift)를 기계어로 변환하여 실행파일로 만드는 과정
  • 빌드 빠르게 하기
    • 빌드 프로세스 병렬화하기 (모듈화 필요)
    • Run Script가 있다면? : Run Script가 빌드할때마다 동작하지 않도록 필요한 inputs, ouputs 작성해주기
    • 복잡한 표현식 다루기
      • xcode가 추론하기 쉽게 클로저에 반환값 작성하기
      • 복잡한 표현식을 한줄로 길게 작성 자제하기
반응형

'WWDC' 카테고리의 다른 글