【问题标题】:Measure strong/weak ARC references impact - Impact on execution time [closed]衡量强/弱 ARC 参考影响 - 对执行时间的影响 [关闭]
【发布时间】: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


【解决方案1】:

真的没有自动内存管理这样的东西。无论您是否使用 ARC,内存都由 retainrelease 管理。区别在于谁编写代码,你还是编译器。有编写的手动内存管理代码,也有ARC编写的手动内存管理代码。从理论上讲,ARC 会完全将相同的retainrelease 命令插入到您的代码中,如果您正确执行此操作,您会插入这些命令。因此,性能差异应该很小。

【讨论】:

  • 没错,让我更具体地说明我在寻找什么......现在编辑我的问题
  • @André 继续前进,看在上帝的份上。
  • 在实践中,ARC 在插入引用计数调用方面通常比人类程序员更保守,在一定程度上影响了性能。然而,为了解决这个问题,它也有一个巧妙的优化,它可以动态替换“return [x autorelease];”。后跟“[x 保留];”在调用者中,没有引用计数。所以有时它会领先,有时它会落后一点。对于任何给定的测试用例,只有分析工具才能确定。
  • @matt 我看到你认为这是一个愚蠢的问题,你最好继续前进
  • @Catfish_Man 正确,我的回答旨在考虑这两个阶段。优化后,假设你没有拉任何“技巧”,ARC插入的retain和release应该就是你应该做的。
猜你喜欢
  • 2014-05-03
  • 1970-01-01
  • 2017-05-19
  • 1970-01-01
  • 2021-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多