compilation - 향상 - 스위프트 컴파일 시간이 왜 이렇게 느린가요?




xcode 컴파일 옵션 (14)

Xcode 6 Beta 6을 사용하고 있습니다.

이것은 지금 당장 나를 괴롭힌 적이 있지만 지금은 거의 사용할 수없는 시점에 도달하고 있습니다.

내 프로젝트는 적당한 크기의 65 Swift 파일과 몇 개의 브리지 된 Objective-C 파일 (실제로 문제의 원인이 아님)을 갖기 시작했습니다.

응용 프로그램에서 거의 사용되지 않는 클래스에 간단한 공백을 추가하는 것과 같이 Swift 파일을 약간 수정 한 것처럼 지정된 대상의 전체 Swift 파일이 다시 컴파일됩니다.

더 깊이 조사한 결과, 컴파일러 시간의 거의 100 %를 차지하는 것은 Xcode가 대상의 모든 Swift 파일에서 swiftc 명령을 실행하는 swiftc 단계입니다.

추가 조사를 수행했으며 기본 컨트롤러로 앱 대리자를 유지하면 컴파일 속도가 매우 빨라지지만 점점 더 많은 프로젝트 파일을 추가할수록 컴파일 시간이 느려지기 시작했습니다.

이제 65 개의 소스 파일 만 있으면 매번 컴파일하는 데 약 8/10 초가 걸립니다. 전혀 빠르지 는 않습니다.

이 문제를 제외 하고이 문제에 대해 이야기하는 게시물을 보지 못했지만 Xcode 6의 이전 버전이었습니다. 따라서 그 경우 내가 유일한 사람인지 궁금합니다.

최신 정보

Alamofire , Euler CryptoSwift 와 같은 GitHub 에서 몇 가지 Swift 프로젝트를 확인했지만 실제로 비교할 충분한 Swift 파일이 없었습니다. 내가 적당한 크기의 프로젝트를 시작한 것으로 밝혀진 유일한 프로젝트는 SwiftHN 이며, 소스 파일이 수십 개인데도 여전히 동일한 것을 확인할 수 있었으며, 하나의 간단한 공간과 전체 프로젝트를 다시 컴파일해야했습니다. 작은 시간 (2/3 초).

분석기와 컴파일이 빠르게 진행되는 Objective-C 코드와 비교할 때, 이것은 스위프트가 큰 프로젝트를 처리 할 수 ​​없을 것 같지만, 내가 틀렸다고 말해주십시오.

Xcode 6 베타 7로 업데이트

여전히 개선이 없습니다. 이 말이 터지기 시작했습니다. Swift에 #import 가 없기 때문에 Apple이 어떻게 이것을 최적화 할 수 있을지 알 수 없습니다.

Xcode 6.3 및 Swift 1.2로 업데이트

Apple은 증분 빌드 (및 기타 많은 컴파일러 최적화)를 추가했습니다. 이러한 이점을 보려면 코드를 Swift 1.2로 마이그레이션해야하지만 Apple은 Xcode 6.3에 도구를 추가하여 다음과 같은 이점을 제공합니다.

하나

내가 한 것처럼 너무 빨리 기뻐하지 마십시오. 빌드 증분을 만드는 데 사용하는 그래프 솔버는 아직 잘 최적화되지 않았습니다.

실제로 함수 서명 변경 사항을 보지 않으므로 한 방법의 블록에 공백을 추가하면 해당 클래스에 따른 모든 파일이 다시 컴파일됩니다.

둘째, 변경 사항이 영향을 미치지 않더라도 다시 컴파일 된 파일을 기반으로 트리를 만드는 것 같습니다. 예를 들어,이 세 클래스를 다른 파일로 옮길 경우

class FileA: NSObject {
    var foo:String?
}
class FileB: NSObject {
    var bar:FileA?
}
class FileC: NSObject {
    var baz:FileB?
}

이제 FileA 를 수정하면 컴파일러는 분명히 FileA 가 다시 컴파일되도록 표시합니다. FileB 를 다시 컴파일 할 것입니다 ( FileA 의 변경 사항에 따라 괜찮을 것입니다). 그러나 FileA 가 다시 컴파일되기 때문에 FileB 이며 FileB 가 여기에서 FileA 사용하지 않기 때문에 꽤 나쁩니다.

의존성 트리 솔버가 개선되기를 바랍니다.이 샘플 코드로 radar 를 열었습니다.

Xcode 7 베타 5 및 Swift 2.0으로 업데이트

어제 Apple은 베타 5를 출시했으며 릴리스 노트에서 다음과 같이 볼 수 있습니다.

Swift Language & Compiler • 증분 빌드 : 함수 본문 만 변경해도 더 이상 종속 파일이 다시 작성되지 않아야합니다. (15352929)

나는 그것을 시도했고 그것이 실제로 (정말!) 잘 작동한다고 말해야합니다. 그들은 증분 빌드를 신속하게 최적화했습니다.

Xcode 7 베타 5를 사용하여 swift2.0 분기를 만들고 코드를 최신 상태로 유지하는 것이 좋습니다. 컴파일러의 향상된 기능에 만족할 것입니다 (그러나 XCode 7의 글로벌 상태는 여전히 느리고 버그가 있다고 말할 것입니다) )

Xcode 8.2로 업데이트

이 문제에 대한 마지막 업데이트 이후 오랜 시간이 지났습니다.

우리의 응용 프로그램은 이제 거의 독점적으로 Swift 코드의 약 20k 줄입니다. 스위프트 2와 스위프트 3 마이그레이션보다 빠릅니다. 2014 년 중반 Macbook Pro (2.5GHz Intel Core i7)에서 컴파일하는 데 약 5 / 6m가 소요되며 이는 깔끔한 빌드에서는 괜찮습니다.

그러나 애플이 다음과 같이 주장하지만 증분 빌드는 여전히 농담입니다.

Xcode는 작은 변경 사항이 발생한 경우 전체 대상을 다시 작성하지 않습니다. (28892475)

분명히 나는 ​​우리 중 많은 사람들 이이 넌센스를 체크 아웃 한 후 웃었다 고 생각합니다 (프로젝트의 파일에 하나의 private (private!) 속성을 추가하면 전체가 다시 컴파일됩니다 ...)

Apple 개발자 포럼 에서이 스레드 에 대해 알려 드리고자합니다. 포럼은 문제에 대한 자세한 정보가 있습니다 (애플은이 문제에 대한 Apple의 커뮤니케이션에 감사드립니다)

기본적으로 사람들은 증분 빌드를 개선하기 위해 몇 가지를 생각해 냈습니다.

  1. HEADER_MAP_USES_VFS 프로젝트 설정을 true 설정하십시오.
  2. 체계에서 Find implicit dependencies 비활성화
  3. 새 프로젝트를 작성하고 파일 계층을 새 프로젝트로 이동하십시오.

솔루션 3을 시도하지만 솔루션 1/2은 우리에게 효과가 없었습니다.

이 전체 상황에서 아이러니하게도 재미있는 것은 우리가 Xcode 6을 사용하고있는이 문제에 대한 첫 번째 게시물을 보면 첫 번째 컴파일 부진에 도달했을 때 swift 1 또는 swift 1.1 코드를 믿으며 약 2 년 후에 Apple의 실제 개선에도 불구하고 상황은 Xcode 6에서와 마찬가지로 나빴습니다.

나는 실제로 매일의 좌절 때문에 프로젝트에 Swift over Obj / C를 선택하는 것을 후회합니다. (나는 심지어 AppCode로 전환하지만 다른 이야기입니다)

어쨌든 나는이 SO 게시물 이이 글을 쓰는 시점에서 32k + 조회수와 143 업을 가지고 있으므로 내가 유일한 사람이 아니라고 생각합니다. 이 상황에 비관적이지만 터널 끝 부분에 약간의 빛이있을 수 있습니다.

시간이 있고 용기가 있다면, 애플이 이것에 대해 레이더를 환영한다고 생각합니다.

다음 시간까지! 건배

Xcode 9로 업데이트

오늘 this 우연히 발견 this . Xcode는 현재 끔찍한 성능을 향상시키기 위해 새로운 빌드 시스템을 조용히 도입했습니다. 작업 공간 설정을 통해 활성화해야합니다.

아직 시도했지만이 게시물이 완료되면 업데이트됩니다. 그래도 유망 해 보인다.


아마도 우리는 Swift 컴파일러를 고칠 수 없지만 고칠 수있는 것은 코드입니다!

Swift 컴파일러에는 숨겨진 모든 옵션이 있습니다.이 옵션은 컴파일러가 모든 단일 함수를 컴파일하는 데 걸리는 정확한 시간 간격을 출력합니다 : -Xfrontend -debug-time-function-bodies . 이를 통해 코드에서 병목 현상을 발견하고 컴파일 시간을 크게 개선 할 수 있습니다.

터미널에서 다음을 간단히 실행하고 결과를 분석하십시오.

xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt

멋진 Brian Irace는 Swift 컴파일 시간 프로파일 링 에 대한 훌륭한 기사를 썼습니다.


Mac을 재부팅하면이 문제가 궁금해졌습니다. 재부팅으로 15 분 빌드에서 30 초 빌드로 갔다.


Xcode 6.3.1에서는 아무것도 효과가 없었습니다 .Arround 100 Swift 파일을 추가했을 때 Xcode가 빌드 및 / 또는 인덱싱에서 임의로 중단되었습니다. 나는 성공하지 못한 모듈 옵션을 시도했다.

Xcode 6.4 Beta를 설치하고 사용하는 것이 실제로 효과적이었습니다.


Xcode 8의 경우 프로젝트 설정으로 이동 한 다음 편집기> 빌드 설정 추가> 사용자 정의 설정 추가로 이동하여 다음을 추가하십시오.

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES

이 플래그를 추가하면 40KLOC 신속한 프로젝트를 위해 클린 빌드 컴파일 시간이 7 분에서 65 초로 줄었습니다. 또한 2 명의 친구가 엔터프라이즈 프로젝트에서 비슷한 개선을 보았 음을 확인할 수 있습니다.

나는 이것이 Xcode 8.0의 일종의 버그라고 가정 할 수 있습니다.

편집 : 일부 사람들에게는 Xcode 8.3에서 더 이상 작동하지 않는 것 같습니다.


디버그 및 테스트를 위해 다음 설정을 사용하여 컴파일 시간을 약 20 분에서 2 분 미만으로 줄이십시오.

  1. 프로젝트 빌드 설정에서 "최적화"를 검색하십시오. 디버그를 "가장 빠른 [-O3]"이상으로 설정하십시오.
  2. 활성 아키텍처에 대한 빌드 설정 : 예
  3. 디버그 정보 형식 : DWARF
  4. 전체 모듈 최적화 : NO

프로젝트를 빌드하기 위해 수많은 시간을 허비하고 있었기 때문에 한 번만 변경하면 테스트를 위해 30 분 더 기다려야했습니다. 이것들은 나를 위해 일한 설정입니다. (나는 여전히 설정을 실험하고 있습니다)

그러나 릴리스 / 아카이빙이 iTunes Connect로 푸시되도록하려면 최소한 "dWAY with dSYM"(응용 프로그램을 모니터링하려는 경우) 및 빌드 활성 아키텍처를 "아니오"로 설정해야합니다 (여기서도 몇 시간을 낭비한 것을 기억하십시오).


또한 디버그 (Swift 또는 Objective-C)를 컴파일 할 때 활성 아키텍처 만 빌드로 설정했는지 확인하십시오.


불행히도 Swift 컴파일러는 (Xcode 6.3 베타 현재) 빠르고 증분 컴파일에 최적화되어 있지 않습니다. 한편 다음 기술 중 일부를 사용하여 Swift 컴파일 시간을 향상시킬 수 있습니다.

  • 재 컴파일 영향을 줄이기 위해 앱을 프레임 워크로 분할하십시오. 그러나 앱에서 주기적 종속성을 피해야합니다. 이 주제에 대한 자세한 내용은 다음 게시물을 확인하십시오. http://bits.citrusbyte.com/improving-swift-compile-time/

  • 매우 안정적이며 자주 변경되지 않는 프로젝트 부분에는 Swift를 사용하십시오. 매우 자주 변경해야하는 영역이나 많은 컴파일 / 실행 반복이 완료되어야하는 영역 (거의 모든 UI 관련 항목)의 경우 Objective-C를 믹스 앤 매치 방식으로 더 잘 사용하십시오.

  • 'Injection for Xcode'로 런타임 코드 삽입을 시도하십시오.

  • roopc 방법 사용 : http://roopc.net/posts/2014/speeding-up-swift-builds/

  • 명시 적 캐스트와 함께 힌트를 제공하여 신속한 형식 유추 엔진을 완화합니다.


솔루션이 캐스팅 중입니다.

나는 다음과 같이 엄청난 양의 사전을 가지고있었습니다.

["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
.....

컴파일하는 데 약 40 분이 걸렸습니다. 내가 이런 식으로 사전을 캐스팅 할 때까지 :

["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
....

이것은 내 응용 프로그램에 하드 코딩 된 데이터 유형과 관련하여 거의 모든 다른 문제에 효과적이었습니다.


우리는 약 100k 라인의 Swift 코드와 300k 라인의 ObjC 코드를 가지고 있기 때문에 이것을 극복하기 위해 몇 가지 노력을 기울였습니다.

첫 번째 단계는 함수 컴파일 시간 출력에 따라 모든 함수를 최적화하는 것이 었습니다 (예 : https://thatthinginswift.com/debug-long-compile-times-swift/ 설명 된대로)

다음으로 모든 신속한 파일을 하나의 파일로 병합하는 스크립트를 작성했습니다. 이로 인해 액세스 수준이 떨어지지 만 컴파일 시간이 5-6 분에서 ~ 1 분으로 단축되었습니다.

Apple에 문의하여 다음과 같은 조치를 취해야한다고 권고했기 때문에 이는 현재 소멸되었습니다.

  1. 'Swift Compiler-Code Generation'빌드 설정에서 '전체 모듈 최적화'를 켜십시오. 'Fast, Whole Module Optimization' 선택하십시오

  1. 'Swift Compiler-Custom Flags'에서 개발 빌드에 '-Onone' 추가하십시오.

이 플래그가 설정되면 컴파일러는 모든 Swift 파일을 한 단계로 컴파일합니다. 병합 스크립트를 사용하면 파일을 개별적으로 컴파일하는 것보다 훨씬 빠릅니다. 그러나 ' -Onone' 재정의가 -Onone' 전체 모듈도 최적화되므로 속도가 느려집니다. 다른 Swift 플래그에 '-Onone' 플래그를 설정하면 최적화가 중지되지만 한 번에 모든 Swift 파일 컴파일이 중지되지는 않습니다.

전체 모듈 최적화에 대한 자세한 내용은 Apple 블로그 게시물 ( https://swift.org/blog/whole-module-optimizations/

이러한 설정을 통해 Swift 코드가 30 초 안에 컴파일 될 수 있다는 것을 알았습니다.-) 다른 프로젝트에서 어떻게 작동하는지에 대한 증거는 없지만 Swift 컴파일 시간이 여전히 문제가되는 경우 시도해 보는 것이 좋습니다.

App Store 빌드의 경우 프로덕션 빌드에 최적화가 권장되므로 '-Onone' 플래그를 남겨 두어야합니다.


이 모든 것들이 베타 버전이고 Swift 컴파일러가 (적어도 현재는) 열리지 않았기 때문에 귀하의 질문에 대한 실제 답변이없는 것 같습니다.

우선 Objective-C와 Swift 컴파일러를 비교하는 것은 다소 잔인합니다. Swift는 여전히 베타 버전이며 Apple은 기능을 제공하고 버그를 수정하는 것보다 번개 속도를 제공하는 것 이상입니다 (가구를 구입하여 집을 짓기 시작하지는 않습니다). 애플이 적시에 컴파일러를 최적화 할 것으로 생각합니다.

어떤 이유로 모든 소스 파일을 완전히 컴파일해야하는 경우 별도의 모듈 / 라이브러리를 작성하는 옵션이있을 수 있습니다. 그러나 언어가 안정 될 때까지 Swift에서 라이브러리를 허용 할 수 없으므로이 옵션은 아직 가능하지 않습니다.

내 생각에 그들은 컴파일러를 최적화 할 것입니다. 사전 컴파일 된 모듈을 만들 수없는 것과 같은 이유로 컴파일러가 모든 것을 처음부터 컴파일해야 할 수도 있습니다. 그러나 언어가 안정적인 버전에 도달하고 바이너리 형식이 더 이상 변경되지 않으면 라이브러리를 만들 수 있으며 컴파일러는 (?) 작업을 최적화 할 수도 있습니다.

그래도 애플 만이 알고있는 것만으로도 ...


컴파일 시간을 늦추는 특정 파일을 식별하려는 경우 xctool 을 통해 명령 줄에서 파일을 컴파일하면 파일별로 컴파일 시간을 줄 수 있습니다.

주목할 것은 기본적으로 각 CPU 코어 당 2 개의 파일을 동시에 빌드하며 "net"경과 시간이 아니라 절대 "user"시간을 제공한다는 것입니다. 이렇게하면 병렬화 된 파일 사이의 모든 타이밍이 매우 유사 해 보이고 매우 유사 해 보입니다.

이를 극복하려면 -jobs 플래그를 1로 설정하여 파일 빌드를 병렬화하지 않도록하십시오. 시간이 오래 걸리지 만 결국에는 파일별로 파일을 비교할 수있는 "net"컴파일 시간이 있습니다.

다음은 트릭을 수행해야하는 명령 예입니다.

xctool -workspace <your_workspace> -scheme <your_scheme> -jobs 1 build

"Swift 파일 컴파일"단계의 결과는 다음과 같습니다.

...Compile EntityObserver.swift (1623 ms)Compile Session.swift (1526 ms)Compile SearchComposer.swift (1556 ms)
...

이 출력에서 ​​컴파일하는 데 다른 파일보다 시간이 오래 걸리는 파일을 빠르게 식별 할 수 있습니다. 또한 리팩토링 (명시 적 캐스트, 유형 힌트 등)이 특정 파일의 컴파일 시간을 단축하는지 여부를 정확하게 확인할 수 있습니다.

참고 : 기술적으로 xcodebuild 하여 수행 할 수도 있지만 출력은 매우 장황하고 소비하기가 어렵습니다.


컴파일러는 형식을 유추하고 확인하는 데 많은 시간을 소비합니다. 따라서 타입 주석을 추가하면 컴파일러에 많은 도움이됩니다.

다음과 같은 체인 함수 호출이 많은 경우

let sum = [1,2,3].map({String($0)}).flatMap({Float($0)}).reduce(0, combine: +)

그런 다음 컴파일러는 sum 유형이 무엇인지 파악하는 데 시간이 걸립니다. 유형을 추가하면 도움이됩니다. 또한 간헐적 인 단계를 별도의 변수로 가져 오는 것이 도움이됩니다.

let numbers: [Int] = [1,2,3]
let strings: [String] = sum.map({String($0)})
let floats: [Float] = strings.flatMap({Float($0)})
let sum: Float = floats.reduce(0, combine: +)

특히 숫자 유형 CGFloat 경우 Int 는 많은 도움이 될 수 있습니다. 2 와 같은 리터럴 숫자는 다양한 숫자 유형을 나타낼 수 있습니다. 따라서 컴파일러는 어떤 컨텍스트인지 파악해야합니다.

+ 처럼 보이는 데 많은 시간이 걸리는 함수도 피해야합니다. 컴파일러가 각 + 에 대해 어떤 + 구현을 호출해야하는지 파악해야하기 때문에 여러 + 를 사용하여 여러 배열을 연결하는 속도가 느립니다. 따라서 가능하다면 append() 와 함께 var a: [Foo] 사용 append() .

Xcode에서 컴파일이 느린 함수 를 감지하는 경고를 추가 할 수 있습니다.

대상의 빌드 설정 에서 다른 스위프트 플래그를 검색하고 추가하십시오.

-Xfrontend -warn-long-function-bodies=100

컴파일하는 데 100ms 이상 걸리는 모든 함수에 대해 경고합니다.


필자의 경우 Xcode 7은 전혀 차이가 없었습니다. 컴파일하는 데 몇 초가 걸리는 여러 함수가 있습니다.

// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

옵션을 풀고 나면 빌드 시간이 99.4 % 감소했습니다.

// Build time: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
    padding += rightView.bounds.width
}

if let leftView = leftView {
    padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)

이 게시물과이 게시물 에서 더 많은 예제를 참조하십시오.

Xcode 용 빌드 시간 분석기

이러한 문제가 발생하는 모든 사람에게 유용한 Xcode 플러그인을 개발했습니다 .

Swift 3에는 개선이있을 것으로 보이므로 Swift 코드가 더 빨리 컴파일되는 것을 볼 수있을 것입니다.


한 가지 주목할 점은 중첩 유형의 경우 스위프트 유형 유추 엔진이 매우 느릴 수 있다는 것입니다. 시간이 오래 걸리는 개별 컴파일 단위의 빌드 로그를보고 전체 Xcode 생성 명령을 복사하여 터미널 창에 붙여 넣은 다음 CTRL- \를 눌러 느려짐의 원인에 대한 일반적인 아이디어를 얻을 수 있습니다. 일부 진단. 전체 예제는 http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times 를 참조하십시오.