关于 Swift 编译时性能优化的一些思考

简介: 本文讲的是关于 Swift 编译时性能优化的一些思考,一行之前很简洁的代码,现在却出现了新的问题——它是否应该重构为9行代码来达到更快的编译速度? (nil coalescing 运算符就是一个例子)孰轻孰重?简洁的代码还是对编译器友好的代码?
本文讲的是关于 Swift 编译时性能优化的一些思考,

上周,我读了 @nickoneill 一篇优秀的帖子 Speeding Up Slow Swift Build Times 之后,我发现用一个略不同以往的角度去读Swift代码,并不是很难。

一行之前很简洁的代码,现在却出现了新的问题——它是否应该重构为9行代码来达到更快的编译速度? (nil coalescing 运算符就是一个例子)孰轻孰重?简洁的代码还是对编译器友好的代码? 我觉得,它取决于项目的大小和开发者的想法。

但请等等... 这里有一个Xcode插件

在讲一些例子之前,我首先想到了通过手工提取日志信息是非常耗时的事情。通过命令行工具实现会相对容易一些,但是我把它往前推进了一步:集成为Xcode插件

在这个例子中,最初的目的仅仅是识别并修复代码中最耗时的地方,但是现在我觉得它成为了一个必须要迭代的过程。这样我才可以更加高效地构建代码,并且防止在项目中出现耗时的函数。

不少惊喜

我经常在不同的 Git 分支中跳转,并且等待一个暖慢的项目编译简直是在浪费我的生命。因此我思考了很长时间,一个玩具项目(大约两万行 Swift 代码)会编译如此长的时间。

当我知道是什么原因导致它如此慢之后,我不得不承认我震惊了,一行代码居然需要几秒的编译时间。

让我们来看几个例子。

Nil 合并运算符

编译器肯定不喜欢这里的第一种方法。在展开下面两处简写的代码之后,构建时间减少了 99.4%

// 构建时间: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

// 构建时间: 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)

ArrayOfStuff + [Stuff]

这个看起来像下面这样:

return ArrayOfStuff + [Stuff]  
// 而不是  
ArrayOfStuff.append(stuff)  
return ArrayOfStuff

我经常这么做,并且它影响了每次构建的时间。下面是最糟糕的一个例子,改写后构建时间可以减少 97.9%

// 构建时间: 1250.3ms
let systemOptions = [ 7, 14, 30, -1 ]
let systemNames = (0...2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")]
// Some code in-between 
labelNames = Array(systemNames[0..<count]) + [systemNames.last!]

// 构建时间: 25.5ms
let systemOptions = [ 7, 14, 30, -1 ]
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat, $0) }
systemNames.append(NSLocalizedString("everything", comment: ""))
// Some code in-between
labelNames = Array(systemNames[0..<count])
labelNames.append(systemNames.last!)

三元运算符

仅仅是通过替换三元运算符为 if else 语句就能减少 92.9% 的构建时间。如果使用一个for循环替换 map 函数,它又能减少另一个 75%(但是我的眼睛可就受不了咯 :wink: )。

// 构建时间: 239.0ms
let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)}

// 构建时间: 16.9ms
var labelNames: [String]
if type == 0 {
    labelNames = (1...5).map{type0ToString($0)}
} else {
    labelNames = (0...2).map{type1ToString($0)}
}

转换 CGFloat 到 CGFloat

这里我所说的并不一定正确。变量已经使用了 CGFloat 并且有一些括号也是多余的。在清理了这些冗余之后,构建时间能减少99.9%

// 构建时间: 3431.7 ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180

// 构建时间: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180

Round()

这个一个非常奇怪的例子,下面的例子中变量是一个局部变量与实例变量的混合。这个问题可能不是四舍五入本身,而是结合代码的方法。去掉四舍五入的方法大概能减少 97.6% 的构建时间。

// 构建时间: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// 构建时间: 34.7ms
let expansion = a — b — c + d * 0.66 + e

注意:所有的测试都在 MacBook Air (13-inch, Mid 2013)中进行。

尝试它

无论你是否面临过构建时间太长的问题,编写对编译器友好的代码都是非常有用的。我确定你自己会在其中找到一些惊喜。作为参考,这里有完整的代码,在我的工程中可以5秒内完成编译...

import UIKit

class CMExpandingTextField: UITextField {

    func textFieldEditingChanged() {
        invalidateIntrinsicContentSize()
    }

    override func intrinsicContentSize() -> CGSize {
        if isFirstResponder(), let text = text {
            let size = text.sizeWithAttributes(typingAttributes)
            return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
        }
        return super.intrinsicContentSize()
    }
}





原文发布时间为:2016年06月12日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
2月前
|
设计模式 安全 测试技术
Swift代码审查的关键点及最佳实践,涵盖代码风格一致性、变量使用合理性、函数设计、错误处理、性能优化、安全性、代码注释等方面,旨在提升代码质量和项目管理水平
本文深入探讨了Swift代码审查的关键点及最佳实践,涵盖代码风格一致性、变量使用合理性、函数设计、错误处理、性能优化、安全性、代码注释等方面,旨在提升代码质量和项目管理水平。通过实际案例分析,展示了如何有效应用这些原则,确保代码的高可读性、可维护性和可靠性。
35 2
|
8月前
|
缓存 算法 Swift
【Swift 开发专栏】Swift 应用的性能优化技巧
【4月更文挑战第30天】本文探讨了Swift应用性能优化,强调理解性能瓶颈、针对性优化和平衡性能与代码质量的重要性。提出优化技巧,包括选择合适数据结构、避免不必要的对象创建、使用缓存、优化算法、减少计算、管理内存、利用多核处理、优化网络请求和界面渲染。通过实际案例分析证明了这些方法能有效提升应用性能和用户体验。开发者应持续关注新技术和方法,以适应不断提升的性能要求。
103 1
|
8月前
|
安全 编译器 Swift
【Swift开发专栏】Swift的编译优化与构建配置
【4月更文挑战第30天】Swift编译优化与构建配置对开发效率和应用性能至关重要。编译优化包括不同级别的优化、函数内联、泛型特化、尾递归优化、死代码消除和链接时优化。在Xcode的&quot;Build Settings&quot;中可调整相关标志。构建配置涉及Debug与Release模式、自定义配置、条件编译、构建设置和脚本。开发时,应适时测试、选择适当优化级别、避免过度优化,并利用条件编译区分不同版本的代码。有效管理构建设置可提升开发质量和性能。
120 0
|
Swift iOS开发 MacOS
Swift-进阶 01:Swift源码编译
Swift-进阶 01:Swift源码编译
532 0
Swift-进阶 01:Swift源码编译
|
JSON 数据格式 iOS开发
[译] 优化 Swift 的编译时间
本文讲的是[译] 优化 Swift 的编译时间,在 Swift 所有的特性中,有一件事有时会相当恼人,那就是在用 Swift 编写更大规模的项目时,它一般会编译多久。尽管 Swift 编译器在保证运行时安全方面做的更多,但是它的编译时间要比 Objective-C 编译时间长很多。
1346 0
|
程序员 Android开发 iOS开发
iOS Swift _Nullable 与 Android 注解帮助编译时检查 - 两家好像步调开始一致一段时间了
iOS Swift _Nullable 与 Android 注解帮助编译时检查 - 两家好像步调开始一致一段时间了 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循“署名-非商业用途-保持一致”创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS、Android、HTML5、Arduino、pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作。
1608 0
|
Web App开发 Ubuntu
《Swift入门》ubuntu下编译运行Swift开发的Web后端示例
这里只是演示如何在ubuntu下编译运行Swift开发的Web后端项目。 项目代码来自Bluemix上提供的示例代码,如果你有账号,可以去自己的空间下载,没有的话,可以通过下面的地址下载: http://download.
870 0
|
Swift
Swift Beta6 编译之前版本出错
安装Xcode6 Beta6之后,编译之前的项目可能会出问题,比如出现__TFSs26_forceBridgeFromObjectiveCU__FTPSs9AnyObject_MQ__Q_的问题,如下图: 遇到这个问题只要把Derived Data清除掉,重新编译就可以了: 这样一来__TFSs26_forceBridgeFromObjectiveCU__FTPSs9AnyObject_MQ__Q_的问题就解决了。
925 0
|
6月前
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
191 3