【发布时间】:2017-04-12 18:58:57
【问题描述】:
我想知道强/弱引用管理是否(以及多少)对代码执行有影响,尤其是在释放许多类可能具有弱引用的对象时。起初我把它误认为是 ARC,但事实并非如此。
在同一主题上有一个类似的question,但是他们没有调查性能影响或尝试从中提取一些数字。
让我明确一点:无论如何,我并不是在暗示 ARC 或强/弱可能会对性能产生不良影响,或者说“不要使用它”。我喜欢这个。我只是好奇它的效率如何,以及如何调整它的大小。
我整理了这段代码,以了解强/弱引用对执行时间的性能影响。
import Foundation
class Experiment1Class {
weak var aClass: Experiment1Class?
}
class Experiment2Class {
var aClass: Experiment2Class?
}
var persistentClass: Experiment1Class? = Experiment1Class()
var nonWeakPersistentClass: Experiment2Class? = Experiment2Class()
var classHolder = [Experiment1Class]()
var nonWeakClassholder = [Experiment2Class]()
for _ in 1...1000 {
let aNewClass = Experiment1Class()
aNewClass.aClass = persistentClass
classHolder.append(aNewClass)
let someNewClass = Experiment2Class()
someNewClass.aClass = nonWeakPersistentClass
nonWeakClassholder.append(someNewClass)
}
let date = Date()
persistentClass = nil
let date2 = Date()
let someDate = Date()
nonWeakPersistentClass = nil
let someDate2 = Date()
let timeExperiment1 = date2.timeIntervalSince(date)
let timeExperiment2 = someDate2.timeIntervalSince(someDate)
print("Time: \(timeExperiment1)")
print("Time: \(timeExperiment2)")
这段代码仅测量释放对象并将其所有引用设置为零所需的时间。
如果您在 Playground (Xcode 8.3.1) 中执行它,您将看到 10:1 的比率,但 Playground 执行比实际执行要慢得多,因此我还建议使用“Release”构建配置执行上述代码。
如果您在 Release 中执行,我建议您至少将迭代计数设置为“1000000”,就像我这样做的方式:
- 将以上代码插入到文件test.swift中
- 从终端运行 swiftc test.swift
- 执行./test
作为这样的测试,我相信绝对的结果意义不大,而且我相信这对 99% 的常用应用程序没有影响......
到目前为止,我的结果显示,在我的 Mac 上执行的发布配置:
Time: 3.99351119995117e-06
Time: 0.0
但是,在我的 iPhone 7Plus 上,在 Release 中执行相同的操作:
Time: 1.4960765838623e-05
Time: 1.01327896118164e-06
很明显,该测试表明,对典型应用的实际影响应该很少甚至根本不用担心。
这是我的问题:
- 您能想出其他方法来衡量强/弱引用对执行时间的影响吗? (我想知道系统采取了哪些策略来改进这一点)
- 还有哪些其他指标可能很重要? (如多线程优化或线程锁定)
- 我该如何改进?
编辑 1
我发现这个 LinkedList 测试非常有趣,有几个原因,请考虑以下代码:
//: Playground - noun: a place where people can play
import Foundation
var n = 0
class LinkedList: CustomStringConvertible {
var count = n
weak var previous: LinkedList?
var next: LinkedList?
deinit {
// print("Muorte \(count)")
}
init() {
// print("Crea \(count)")
n += 1
}
var description: String {
get {
return "Node \(count)"
}
}
func recurseDesc() -> String {
return(description + " > " + (next?.recurseDesc() ?? "FIN"))
}
}
func test() {
var controlArray = [LinkedList]()
var measureArray = [LinkedList]()
var currentNode: LinkedList? = LinkedList()
controlArray.append(currentNode!)
measureArray.append(currentNode!)
var startingNode = currentNode
for _ in 1...31000 {
let newNode = LinkedList()
currentNode?.next = newNode
newNode.previous = currentNode!
currentNode = newNode
controlArray.append(newNode)
measureArray.append(newNode)
}
controlArray.removeAll()
measureArray.removeAll()
print("test!")
let date = Date()
currentNode = nil
let date2 = Date()
let someDate = Date()
startingNode = nil
let someDate2 = Date()
let timeExperiment1 = date2.timeIntervalSince(date)
let timeExperiment2 = someDate2.timeIntervalSince(someDate)
print("Time: \(timeExperiment1)")
print("Time: \(timeExperiment2)")
}
test()
我发现了以下内容(在发布配置中运行):
- 我无法在我的手机上运行超过 32000 次迭代,它在 deinit 期间崩溃了 EXC_BAD_ACCESS(是的,在 DEINIT 期间......这不是很奇怪)
- ~32000 次迭代的时间是 0 秒对 0.06 秒,这是巨大的!
我认为这个测试非常占用 CPU,因为节点一个接一个地被释放(如果你看到代码,只有下一个是强的,前一个是弱的)......所以一旦第一个被释放,其他人一个接一个地倒下,但不是全部倒下。
【问题讨论】:
-
仅供参考 - 与使用 MRC 的影响相比,使用 ARC 的影响(如果有的话)微不足道,因为调试内存问题的时间要多得多,而且由于内存管理不正确导致应用程序崩溃的次数要多得多。
-
当然@maddy,我绝不会建议不要使用ARC。我只是想知道这台机器的效率如何。让我更具体地回答我的问题。
-
你已经给出了自己的答案,为什么不停下来呢?
-
我最后问了三个问题:ARC 可能产生影响的其他方式、其他指标以及我衡量它的方式的改进@matt
-
我可以解释为什么您的链表示例崩溃了!这是因为你的堆栈溢出了。正如您所提到的,“因此,一旦第一个被释放,其他的就会一个接一个地下降,但并非完全如此。”
node必须先 deinit(release)node->next元素,然后才能完全取消初始化(从 deinit 方法返回),因此会导致递归调用链。您在链表中获得的元素越多,您的堆栈就越深。在某个时刻,您的堆栈用完并崩溃!
标签: ios swift performance automatic-ref-counting