[swift] 스위프트 컴파일 시간이 왜 이렇게 느린가요?

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

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

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

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

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

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

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

나는 제외하고는이 문제에 대해 이야기 사후 보지 못했지만 이 일을 하지만 그 경우에 하나의이야 만약 내가 궁금하네요 그래서 엑스 코드 (6)의 이전 버전을했다.

최신 정보

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

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

Xcode 6 베타 7로 업데이트

여전히 개선이 없습니다. 이 말이 터지기 시작했습니다. #import스위프트 (Swift) 가 없기 때문에 애플이 어떻게 이것을 최적화 할 수 있을지 모르겠다.

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), 그러나 또한 FileC있기 때문에 FileB다시 컴파일하고 있기 때문에 그것은 아주 나쁜는 FileC절대 사용하지 않습니다 FileA여기.

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

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

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

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

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

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

Xcode 8.2로 업데이트

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

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

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

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

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

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에서와 마찬가지로 나빴습니다.

사실은 정말 그것 때문에 포함 매일 좌절의 우리의 프로젝트의 Obj / C를 통해 스위프트을 선택 후회. (나는 심지어 AppCode로 전환하지만 다른 이야기입니다)

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

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

다음 시간까지! 건배

Xcode 9로 업데이트

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

여기에 이미지 설명을 입력하십시오

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



답변

롭 네이피어가 옳다는 것이 밝혀졌습니다. 컴파일러가 berzek으로 이동하게하는 것은 하나의 단일 파일 (실제로 하나의 방법)이었습니다.

이제 내가 틀리지 마 Swift는 매번 모든 파일을 다시 컴파일하지만, 이제는 Apple이 컴파일하는 파일에 실시간 컴파일 피드백을 추가하여 Xcode 6 GM이 컴파일중인 Swift 파일과 컴파일 상태를 실시간으로 보여줍니다. 이 스크린 샷에서 볼 수 있듯이

여기에 이미지 설명을 입력하십시오

따라서 어느 파일이 오래 걸리는지 아는 것이 매우 편리합니다. 제 경우에는 다음과 같은 코드였습니다.

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : self.title ?? ""
        ])

return dic.copy() as NSDictionary

속성이 있기 때문에 title유형이었다 var title:String?하지 NSString. 에 추가 할 때 컴파일러가 열광했습니다 NSMutableDictionary.

로 변경 :

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : NSString(string: self.title ?? "")
        ])

return dic.copy() as NSDictionary

컴파일이 10/15 초 (아마도 더 많음)에서 1 초까지 … 놀랍습니다.


답변

우리는 약 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 플래그에 플래그를 설정하면 최적화가 중지되지만 한 번에 모든 Swift 파일 컴파일이 중지되지는 않습니다.

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

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

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


답변

프로젝트 크기와 거의 관련이 없습니다. 아마도 특정 코드 조각 일 수도 있고 아마도 한 줄일 수도 있습니다. 전체 프로젝트가 아닌 한 번에 하나의 파일을 컴파일하여이를 테스트 할 수 있습니다. 또는 빌드 로그를보고 어떤 파일이 오래 걸리는지 확인하십시오.

문제를 일으킬 수있는 코드 종류의 예로, 이 38 줄 짜리 요점 은 베타 7에서 컴파일하는 데 1 분 이상 걸립니다. 이 모든 것은이 하나의 블록으로 인해 발생합니다 :

let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
  |> sorted~~ { $1 < $0 }
  |> map~~ { $0.description }
  |> joinedWithCommas

한두 줄만으로 단순화하면 거의 즉시 컴파일됩니다. 문제는 컴파일러에서 지수 성장 (아마도 계승 성장)을 일으키는 것입니다. 분명히 이상적이지는 않으며 그러한 상황을 격리 할 수 ​​있다면 레이더를 열어서 문제를 해결하는 데 도움이됩니다.


답변

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

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

이를 극복하려면 파일 빌드를 병렬화하지 않도록 플래그를 1로 설정하십시오-jobs . 시간이 더 걸리지 만 결국에는 파일별로 파일을 비교할 수있는 “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하지만 출력은 매우 장황하고 소비하기가 어렵습니다.


답변

필자의 경우 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 용 빌드 시간 분석기

나는 엑스 코드 플러그에서 개발 된 이러한 문제가 발생하는 누군가를 위해 유용하게 사용할 수 있습니다.

영상

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


답변

아마도 우리는 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 컴파일 시간 프로파일 링 에 대한 훌륭한 기사를 썼습니다 .


답변

해결책은 캐스팅 중입니다.

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

["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],
....

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