219

我正在使用 Xcode 6 Beta 6。

这是困扰我一段时间的事情,但它已经到了现在几乎无法使用的地步。

我的项目开始有65 个Swift文件和一些桥接的 Objective-C 文件(这真的不是问题的原因)。

似乎对任何 Swift 文件的任何轻微修改(例如在应用程序中几乎不使用的类中添加一个简单的空格)都会导致指定目标的整个 Swift 文件被重新编译。

经过更深入的调查,我发现几乎 100% 的编译器时间是CompileSwiftXcode 在swiftc目标的所有 Swift 文件上运行命令的阶段。

我做了一些进一步的调查,如果我只使用默认控制器保留应用程序委托,编译速度非常快,但是随着我添加越来越多的项目文件,编译时间开始变得非常慢。

现在只有 65 个源文件,每次编译大约需要 8/10 秒。一点也不

除了这个,我没有看到任何关于这个问题的帖子,但它是 Xcode 6 的旧版本。所以我想知道我是否是唯一一个在这种情况下。

更新

我在GitHub 上查看了一些 Swift 项目,例如AlamofireEulerCryptoSwift,但没有一个项目有足够的 Swift 文件来进行实际比较。我发现的唯一一个大小合适的项目是SwiftHN 即使它只有十几个源文件,我仍然能够验证相同的东西,一个简单的空间,整个项目需要重新编译,这开始需要很少的时间(2/3 秒)。

与分析器和编译速度都非常快的 Objective-C 代码相比,这真的感觉 Swift 永远无法处理大型项目,但请告诉我我错了。

更新 Xcode 6 Beta 7

仍然没有任何改善。这开始变得荒谬了。由于缺乏#importSwift,我真的不知道 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),FileC因为FileB被重新编译,这非常糟糕,因为FileC从不FileA在这里使用。

所以我希望他们改进依赖树求解器......我已经用这个示例代码打开了一个雷达。

更新 Xcode 7 beta 5 和 Swift 2.0

昨天 Apple 发布了 beta 5,在发行说明中我们可以看到:

Swift 语言和编译器 • 增量构建:仅更改函数体不应再导致依赖文件重新构建。(15352929)

我已经尝试过了,我必须说它现在真的(真的!)运作良好。他们极大地优化了 swift 中的增量构建。

我强烈建议您创建一个swift2.0分支并使用 XCode 7 beta 5 使您的代码保持最新。您会对编译器的增强感到高兴(但是我会说 XCode 7 的全局状态仍然很慢且有问题)

使用 Xcode 8.2 更新

自从我上次更新这个问题以来已经有一段时间了,所以在这里。

我们的应用程序现在大约有 20k 行几乎完全是 Swift 代码,这很不错,但并不出色。它经历了 swift 2 和 swift 3 迁移。在 2014 年中期的 Macbook pro(2.5 GHz Intel Core i7)上编译大约需要 5/6m,这在干净的构建上是可以的。

然而,尽管 Apple 声称,增量构建仍然是一个笑话:

当仅发生很小的更改时,Xcode 不会重建整个目标。(28892475)

显然,我认为我们中的许多人在检查完这些废话后只是笑了(向我的项目的任何文件添加一个私有(私有!)属性将重新编译整个事情......)

我想向你们指出苹果开发者论坛上的这个帖子,其中包含有关该问题的更多信息(以及不时感谢苹果开发者就此事进行的交流)

基本上人们已经想出了一些东西来尝试改进增量构建:

  1. 添加HEADER_MAP_USES_VFS项目设置集true
  2. Find implicit dependencies从您的方案中禁用
  3. 创建一个新项目并将文件层次结构移动到新项目。

我会尝试解决方案 3,但解决方案 1/2 对我们不起作用。

在整个情况下具有讽刺意味的是,在查看有关此问题的第一篇文章时,我们使用 Xcode 6 和我相信 swift 1 或 swift 1.1 代码,当我们达到第一次编译时,现在大约两年后,尽管 Apple 进行了实际改进情况和 Xcode 6 一样糟糕。多么讽刺。

实际上,我真的很后悔为我们的项目选择 Swift 而不是 Obj/C,因为它每天都会带来挫败感。(我什至切换到 AppCode 但这是另一回事)

无论如何,我看到这篇 SO 帖子在撰写本文时有 32k+ 的浏览量和 143 次浏览量,所以我想我不是唯一一个。尽管对这种情况持悲观态度,但坚持下去,隧道尽头可能会有一些曙光。

如果你有时间(和勇气!),我猜 Apple 对此表示欢迎。

直到下次!干杯

使用 Xcode 9 更新

今天偶然发现了这个。Xcode 悄悄地引入了一个新的构建系统来改进当前糟糕的性能。您必须通过工作区设置启用它。

在此处输入图像描述

已经尝试过了,但完成后会更新这篇文章。不过看起来很有希望。

4

22 回答 22

71

好吧,事实证明 Rob Napier 是对的。是一个文件(实际上是一种方法)导致编译器出错。

现在不要误会我的意思。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是 typevar 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 秒(甚至更多)缩短到一秒……太棒了。

于 2014-09-14T12:25:57.993 回答
43

我们已经尝试了很多方法来解决这个问题,因为我们有大约 10 万行 Swift 代码和 30 万行 ObjC 代码。

我们的第一步是根据函数编译时间输出优化所有函数(例如这里描述的https://thatthinginswift.com/debug-long-compile-times-swift/

接下来我们编写了一个脚本将所有 swift 文件合并到一个文件中,这打破了访问级别,但它使我们的编译时间从 5-6 分钟缩短到了 1 分钟左右。

这现在已经不复存在了,因为我们向 Apple 询问了这个问题,他们建议我们应该执行以下操作:

  1. 在“Swift Compiler - Code Generation”构建设置中打开“whole module optimization”。选择'Fast, Whole Module Optimization'

在此处输入图像描述

  1. 在“Swift 编译器 - 自定义标志”中,对于您的开发版本,添加'-Onone'

在此处输入图像描述 在此处输入图像描述

设置这些标志后,编译器将一步编译所有 Swift 文件。我们发现我们的合并脚本比单独编译文件要快得多。但是,如果没有 '-Onone'覆盖,它也会优化整个模块,速度较慢。当我们'-Onone'在其他 Swift 标志中设置该标志时,它会停止优化,但不会停止一步编译所有 Swift 文件。

有关整个模块优化的更多信息,请在此处查看 Apple 的博客文章 - https://swift.org/blog/whole-module-optimizations/

我们发现这些设置允许我们的 Swift 代码在 30 秒内编译 :-) 我没有证据表明它如何在其他项目上工作,但如果 Swift 编译时间对你来说仍然是个问题,我建议尝试一下。

请注意,对于您的 App Store 构建,您应该忽略该'-Onone'标志,因为建议对生产构建进行优化。

于 2016-11-02T00:02:41.920 回答
34

它可能与您的项目规模无关。它可能是一些特定的代码,甚至可能只有一行。您可以通过尝试一次编译一个文件而不是整个项目来测试这一点。或者尝试查看构建日志以查看哪个文件花费了这么长时间。

作为可能导致麻烦的代码种类的一个例子,这个 38 行的要点在 beta7 中编译需要一分钟多的时间。这一切都是由这一块引起的:

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

只需一两行即可简化它,它几乎可以立即编译。问题在于这会导致编译器中的指数增长(可能是阶乘增长)。显然这并不理想,如果你能隔离这种情况,你应该打开雷达来帮助清理这些问题。

于 2014-09-06T22:12:23.633 回答
33

如果您正在尝试识别会减慢编译时间的特定文件,您可以尝试通过xctool从命令行编译它,这将为您提供逐个文件的编译时间。

需要注意的是,默认情况下,它会为每个 CPU 内核同时构建 2 个文件,并且不会给你“净”经过的时间,而是绝对的“用户”时间。这样,并行文件之间的所有时间都会均匀,并且看起来非常相似。

为了克服这个问题,-jobs标志设置为 1,这样它就不会并行化文件构建。这将花费更长的时间,但最终您将拥有“净”编译时间,您可以逐个文件进行比较。

这是一个可以解决问题的示例命令:

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但输出非常冗长且难以使用。

于 2015-07-24T15:27:54.673 回答
29

就我而言,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 代码编译得更快。

于 2016-05-20T12:19:38.397 回答
21

可能我们无法修复 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 写了一篇关于它的精彩文章Profiling your Swift compile times

于 2016-04-14T18:17:41.570 回答
16

解决方案是铸造。

我有一大堆字典,像这样:

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

这几乎适用于我遇到的关于我硬编码到我的应用程序中的数据类型的所有其他问题。

于 2015-01-31T19:42:53.537 回答
15

需要注意的一件事是 Swift 类型推理引擎对于嵌套类型可能非常慢。您可以通过查看需要很长时间的各个编译单元的构建日志,然后将完整的 Xcode 生成的命令复制并粘贴到终端窗口中,然后按 CTRL-\ 来获得有关导致缓慢的一般概念一些诊断。查看http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times以获得完整示例。

于 2014-10-18T16:57:28.957 回答
9

还要确保在编译调试(Swift 或 Objective-C)时,设置为 Build Active Architecture Only:

在此处输入图像描述

于 2014-09-11T04:53:34.597 回答
7

由于所有这些东西都处于测试阶段,并且由于 Swift 编译器(至少截至今天)尚未开放,我想您的问题没有真正的答案。

首先,将 Objective-C 与 Swift 编译器进行比较在某种程度上是残酷的。Swift 仍处于测试阶段,我确信 Apple 致力于提供功能和修复错误,而不是提供闪电般的速度(你不会通过购买家具开始建造房屋)。我猜苹果会在适当的时候优化编译器。

如果由于某种原因必须完全编译所有源文件,则可以选择创建单独的模块/库。但是这个选项还不可能,因为在语言稳定之前,Swift 不能允许库。

我的猜测是他们会优化编译器。出于同样的原因,我们无法创建预编译模块,很可能编译器需要从头开始编译所有内容。但是一旦语言达到稳定版本并且二进制文件的格式不再改变,我们将能够创建我们的库,也许(?)编译器也将能够优化它的工作。

不过,只是猜测,因为只有 Apple 知道......

于 2014-09-05T16:18:10.543 回答
5

对于 Xcode 8,转到项目设置,然后编辑器 > 添加构建设置 > 添加用户定义的设置,并添加以下内容:

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES

添加这个标志将我们的干净构建编译时间从 7 分钟减少到 65 秒,对于 40KLOC 快速项目,奇迹般地。也可以确认 2 位朋友在企业项目上看到了类似的改进。

我只能假设这是 Xcode 8.0 中的某种错误

编辑:对于某些人来说,它似乎不再适用于 Xcode 8.3。

于 2016-10-09T23:23:33.180 回答
4

不幸的是,Swift 编译器仍然没有针对快速和增量编译进行优化(从 Xcode 6.3 beta 开始)。同时,您可以使用以下一些技术来改善 Swift 编译时间:

  • 将应用程序拆分为框架以减少重新编译的影响。但请注意,您必须避免应用程序中的循环依赖。有关此主题的更多信息,请查看此帖子:http ://bits.citrusbyte.com/improving-swift-compile-time/

  • 将 Swift 用于项目中相当稳定且不经常更改的部分。对于需要经常更改的其他区域或需要大量编译/运行迭代才能完成的区域(几乎所有与 UI 相关的东西),最好使用具有混合匹配方法的 Objective-C。

  • 使用“Xcode 注入”尝试运行时代码注入

  • 使用 roopc 方法:http ://roopc.net/posts/2014/speeding-up-swift-builds/

  • 通过显式强制转换给出一些提示来缓解快速类型推理引擎。

于 2015-02-27T13:24:53.817 回答
4

Swift 数组和字典的构建似乎是一个非常受欢迎的原因(特别是对于那些来自Ruby背景的人),也就是说,

var a = ["a": "b",
         "c": "d",
         "e": "f",
         "g": "h",
         "i": "j",
         "k": "l",
         "m": "n",
         "o": "p",
         "q": "r",
         "s": "t",
         "u": "v",
         "x": "z"]

可能是应该解决它的原因:

var a = NSMutableDictionary()
a["a"] = "b"
a["c"] = "d"
... and so on
于 2015-04-04T17:27:58.207 回答
4

对于调试和测试,请确保使用以下设置将编译时间从大约 20 分钟减少到不到 2 分钟,

  1. 在项目构建设置中,搜索“优化”将调试转为“最快[-O3]”或更高版本。
  2. 为活动架构设置构建:是
  3. 调试信息格式:DWARF
  4. 整模块优化:否

我浪费了无数个小时等待项目构建,结果我意识到我必须做出一点改变,并且不得不再等 30 分钟来测试它。这些是对我有用的设置。(我仍在尝试设置)

但是,请确保您至少将“DWARF with dSYM”(如果您想监控您的应用程序)和 Build Active Architecture 设置为“NO”,以便发布/存档推送到 iTunes Connect(我记得在这里也浪费了几个小时)。

于 2015-06-30T01:37:25.430 回答
4

对于混合了 Objective-C 和 Swift 代码的项目,我们可以-enable-bridging-pchOther Swift Flags. 这样,桥接头只解析一次,结果(一个临时的“预编译头”或“PCH”文件)被缓存并在目标中的所有 Swift 文件中重用。苹果声称它将构建时间减少了 30%。参考链接:

注意:这仅适用于 Swift 3.1 及更高版本。

于 2017-01-30T18:57:57.183 回答
4

编译器花费大量时间来推断和检查类型。所以添加类型注释对编译器有很大帮助。

如果您有很多链接的函数调用,例如

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: +)

特别是对于数字类型CGFloatInt它可以提供很多帮助。像这样的文字数字2可以表示许多不同的数字类型。所以编译器需要从上下文中找出它是哪一个。

+还应避免使用需要大量时间查找的函数。使用几个+来连接多个数组很慢,因为编译器需要确定+应该为每个数组调用哪个实现+。因此,如果可能,请使用var a: [Foo]with 。append()

你可以添加一个警告来检测哪些函数在 Xcode 中编译速度很慢

Build Settings中为您的目标搜索Other Swift Flags并添加

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

对编译时间超过 100 毫秒的每个函数发出警告。

于 2016-12-12T17:42:30.020 回答
2

在一个表达式中混合整数文字和浮点文字也会导致编译时间长。

1.0 + (1.0 + (1  * (1.0 + 1.0))) // 3429ms

1.0 + (1.0 + (1.0  * (1.0 + 1.0))) // 5ms

许多 1000+ms 编译时表达式在我放一个.0after 整数文字后减少到 10~100ms。

于 2020-06-05T03:33:42.727 回答
2

重新启动我的 Mac 为这个问题带来了奇迹。仅通过重新启动,我就从 15 分钟构建到 30 秒构建。

于 2015-07-19T16:52:04.637 回答
1

新 Xcode 6.3 中改进了Swift 编译时间

编译器改进

Swift 1.2 编译器经过精心设计,更加稳定并在各个方面都提高了性能。在 Xcode 中使用 Swift 时,这些更改还提供了更好的体验。一些最明显的改进包括:

增量构建

默认情况下,未更改的源文件将不再重新编译,这将显着缩短大多数常见情况的构建时间。对代码进行较大的结构更改可能仍需要重建多个文件。

更快的可执行文件

调试构建生成运行速度相当快的二进制文件,新的优化提供了更好的发布构建性能。

更好的编译器诊断

更清晰的错误和警告消息,以及新的 Fix-it,使得编写正确的 Swift 1.2 代码变得更加容易。

稳定性改进

最常见的编译器崩溃已得到修复。您还应该在 Xcode 编辑器中看到更少的 SourceKit 警告。

于 2015-02-10T00:09:58.360 回答
0

在 Xcode 6.3.1 中没有什么对我有用 - 当我添加了大约 100 个 Swift 文件时,Xcode 随机挂在构建和/或索引上。我尝试了一个模块化选项,但没有成功。

安装和使用 Xcode 6.4 Beta实际上对我有用。

于 2015-05-15T06:31:43.653 回答
0

这是另一种可能导致类型推断速度大幅下降的情况。合并运营商

更改行如:

abs(some_optional_variable ?? 0)

abs((some_optional_variable ?? 0) as VARIABLE_TYPE)

帮助将我的编译时间从 70 秒缩短到 13 秒

于 2015-06-08T15:48:31.267 回答
0

这对我来说就像魔术一样 -加速 Swift 编译。它将编译时间从 10 分钟减少到 3 分钟。

它说您应该Whole Module Optimization在添加-Onone时打开Other Swift Flags

我正在Swift 3使用Xcode 8.3/Xcode 8.2

于 2017-04-10T14:18:36.643 回答