为什么Swift编译时间这么慢?

apo*_*che 201 compilation swift

我正在使用Xcode 6 Beta 6.

这是一段时间以来一直困扰着我的事情,但现在已经达到了几乎无法使用的程度.

我的项目开始有一个相当大的65个Swift文件和一些桥接的Objective-C文件(这实际上不是问题的原因).

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

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

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

现在只有65个源文件,每次编译大约需要8/10秒.根本不是很快.

我还没有看到任何帖子在谈论除了这个问题这一个,但它是一个旧版本的Xcode 6.所以我想知道如果我在这种情况下,唯一的一个.

UPDATE

我在GitHub上检查了一些像Alamofire,EulerCryptoSwift的 Swift项目,但没有一个项目有足够的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?
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你修改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代码,这是不错的但不是很出色.它经历了迅速的2而不是迅速的3迁移.在2014年中期的Macbook pro(2.5 GHz Intel Core i7)上编译需要大约5/6m,这在干净的构建上是可以的.

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

当只发生小的变化时,Xcode不会重建整个目标.(28892475)

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

我想在Apple开发者论坛上向大家介绍这个主题,其中包含有关该问题的更多信息(以及赞赏苹果开发者此事一段时间内的沟通)

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

  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 over Obj/C作为我们的项目,因为它涉及到日常的挫败感.(我甚至切换到AppCode,但这是另一个故事)

无论如何我看到这篇SO帖子在撰写本文时有32k +次观看次数和143次以上,所以我想我不是唯一一个.虽然对这种情况持悲观态度,但在隧道尽头可能还有一些亮点.

如果你有时间(和勇气!)我猜苹果欢迎雷达这个.

直到下一次!干杯

更新Xcode 9

今天偶然发现了这一点.Xcode悄然引入了一个新的构建系统,以改善当前可怕的性能.您必须通过工作区设置启用它.

在此输入图像描述

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

apo*_*che 69

好吧,事实证明Rob Napier是对的.这是一个单个文件(实际上是一个方法)导致编译器进入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
Run Code Online (Sandbox Code Playgroud)

因为属性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
Run Code Online (Sandbox Code Playgroud)

使编辑从10/15秒(甚至更多)下降到一秒......令人惊讶.

  • 感谢您跟进答案.对于那些追逐类型推理引擎在编译过程中陷入困境的人来说,这可能非常有用. (2认同)
  • 您需要打开调试助手(CMD + 8)并单击当前构建 (2认同)

Sam*_*tow 40

我们已经尝试了很多东西来解决这个问题,因为我们有大约10万行Swift代码和300k行ObjC代码.

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

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

这已经不存在了,因为我们向Apple询问了这个问题,他们建议我们应该做以下事情:

  1. 在"Swift编译器 - 代码生成"构建设置中打开"整个模块优化".选择'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'标记,因为建议对生产版本进行优化.

  • 非常感谢您的建议!我只是不明白为什么在官方消息来源中没有这样的东西(至少很容易找到),例如你提到的文章应该(必须!)有关于`-Onone`的评论.我们现在不能使用整个模块优化,因为它会使编译器崩溃......但是你的建议几乎可以提高x10的构建速度.在MacBook Air上(2013年)它正在建设大约8分钟,现在已经下降到大约1分钟和一半的时间它在目标之间切换(我们有应用程序,扩展和一些内部框架)和编译故事板 (4认同)

And*_*ega 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)
...
Run Code Online (Sandbox Code Playgroud)

通过此输出,您可以快速识别哪些文件比其他文件花费的时间更长.此外,您可以高精度地确定您的重构(显式强制转换,类型提示等)是否正在降低特定文件的编译时间.

注意:从技术上讲,你也可以使用,xcodebuild但输出非常冗长且难以使用.


Rob*_*ier 32

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

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

let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
  |> sorted~~ { $1 < $0 }
  |> map~~ { $0.description }
  |> joinedWithCommas
Run Code Online (Sandbox Code Playgroud)

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

  • 您可以使用`swiftc`编译单个文件以查看它们需要多长时间. (10认同)

Rob*_*son 26

就我而言,Xcode 7没有任何区别.我有多个函数需要几秒钟来编译.

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

打开选项后,构建时间下降了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)
Run Code Online (Sandbox Code Playgroud)

请参阅本文本文中的更多示例.

为Xcode构建时间分析器

开发了一个Xcode插件,对于遇到这些问题的人来说可能会派上用场.

图片

Swift 3似乎有所改进,所以希望我们能够更快地看到我们的Swift代码编译.


Val*_*gin 18

可能我们无法修复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
Run Code Online (Sandbox Code Playgroud)

令人敬畏的Brian Irace撰写了精彩的文章,分析了你的Swift编译时间.

  • 对于那些先使用zsh`alias grep ='noglob grep'`的用户,否则grep将不起作用 (2认同)

Yic*_*man 16

解决方案是铸造.

我有大量的字典,像这样:

["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
.....
Run Code Online (Sandbox Code Playgroud)

编译它大约需要40分钟.直到我输出这样的字典:

["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
....
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,它是你为改善编译时间所做的优化的一部分,但仍然是当前swift编译器的主要问题,它仍然是每次进行最轻微的修改时重新编译所有单个swift文件. (6认同)
  • 如果它不那么悲伤那将是有趣的. (4认同)
  • 这解决了我的问题,谢谢! (2认同)

mar*_*rux 15

需要注意的一点是,嵌套类型的Swift类型推理引擎可能非常慢.通过观察需要很长时间的单个编译单元的构建日志,然后将完整的Xcode-spawned命令复制并粘贴到终端窗口然后按CTRL-来获取,可以大致了解造成缓慢的原因一些诊断.请查看http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times以获取完整示例.


Riv*_*era 9

还要确保在编译调试(Swift或Objective-C)时,设置为仅构建活动体系结构:

在此输入图像描述


Geo*_*rge 6

由于所有这些东西都在Beta中,并且由于Swift编译器(至少截至今天)没有打开,我想你的问题没有真正的答案.

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

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

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

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


Chr*_*ris 5

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

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES
Run Code Online (Sandbox Code Playgroud)

添加此标志会使我们的清理构建编译时间从7分钟下降到65秒,这对于40KLOC swift项目来说是奇迹般的.也可以确认2个朋友在企业项目上看到了类似的改进.

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

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

  • 请问"项目设置"在哪里? (2认同)