Swift 弱引用比强引用慢得多

2023-11-24

我正在用 Swift 构建一个物理引擎。在对引擎进行了一些最近的添加并运行基准测试后,我注意到性能大幅下降。例如,在下面的屏幕截图中,您可以看到 FPS 如何从 60 FPS 下降到 3 FPS(FPS 位于右下角)。最终,我将问题追溯到一行代码:

final class Shape {
    ...
    weak var body: Body! // This guy
    ...
}

在我的补充中的某个时刻,我添加了来自Shape类到Body班级。这是为了防止强引用循环,因为Body也有很强的参考意义Shape.

不幸的是,弱引用似乎有很大的开销(我想将其消除的额外步骤)。我决定通过构建下面的物理引擎的大规模简化版本并对不同的参考类型进行基准测试来进一步研究这一点。


import Foundation

final class Body {
    let shape: Shape
    var position = CGPoint()
    init(shape: Shape) {
        self.shape = shape
        shape.body = self
        
    }
}

final class Shape {
    weak var body: Body! //****** This line is the problem ******
    var vertices: [CGPoint] = []
    init() {
        for _ in 0 ..< 8 {
            self.vertices.append( CGPoint(x:CGFloat.random(in: -10...10), y:CGFloat.random(in: -10...10) ))
        }
    }
}

var bodies: [Body] = []
for _ in 0 ..< 1000 {
    bodies.append(Body(shape: Shape()))
}

var pairs: [(Shape,Shape)] = []
for i in 0 ..< bodies.count {
    let a = bodies[i]
    for j in i + 1 ..< bodies.count {
        let b = bodies[j]
        pairs.append((a.shape,b.shape))
    }
}

/*
 Benchmarking some random computation performed on the pairs.
 Normally this would be collision detection, impulse resolution, etc.
 */
let startTime = CFAbsoluteTimeGetCurrent()
for (a,b) in pairs {
    var t: CGFloat = 0
    for v in a.vertices {
        t += v.x*v.x + v.y*v.y
    }
    for v in b.vertices {
        t += v.x*v.x + v.y*v.y
    }
    a.body.position.x += t
    a.body.position.y += t
    b.body.position.x -= t
    b.body.position.y -= t
}
let time = CFAbsoluteTimeGetCurrent() - startTime

print(time)

Results

以下是每种参考类型的基准时间。在每次测试中,body参考Shape班级被改变了。该代码是使用发布模式 [-O] 和面向 macOS 10.15 的 Swift 5.1 构建的。

weak var body: Body!:0.1886秒

var body: Body!:0.0167秒

unowned body: Body!:0.0942秒

您可以看到在上面的计算中使用强引用而不是弱引用会导致性能提高 10 倍以上。使用unowned有帮助,但不幸的是它仍然慢 5 倍。通过探查器运行代码时,似乎会执行额外的运行时检查,从而导致大量开销。

所以问题是,我有什么选择可以让一个简单的返回指针指向 Body 而不会产生这种 ARC 开销。此外,为什么这种开销看起来如此极端?我想我可以保留强引用循环并手动打破它。但我想知道是否有更好的选择?

Update:根据答案,这是结果
unowned(unsafe) var body: Body!:0.0160秒

Update2:从 Swift 5.2 (Xcode 11.4) 开始,我注意到 unowned(unsafe) 的开销要大得多。这是现在的结果unowned(unsafe) var body: Body!:0.0804秒

注意:从 Xcode 12/Swift 5.3 开始仍然如此


当我撰写/调查这个问题时,我最终找到了解决方案。拥有一个简单的后退指针,无需进行开销检查weak or unowned您可以将 body 声明为:

unowned(unsafe) var body: Body!

根据 Swift 文档:

Swift 还为您需要的情况提供不安全的无主引用 禁用运行时安全检查——例如,出于性能原因。 与所有不安全操作一样,您承担以下责任: 检查该代码的安全性。

您可以通过编写 unowned(unsafe) 来指示不安全的无主引用。 如果您尝试在实例之后访问不安全的无主引用 它所指的已被释放,您的程序将尝试访问 实例曾经所在的内存位置,这是不安全的 手术

因此,很明显,这些运行时检查可能会在性能关键型代码中产生严重的开销。

Update: 从 Swift 5.2 (Xcode 11.4) 开始,我注意到unowned(unsafe)有更多的开销。我现在只是简单地使用强引用并手动中断保留周期,或者尝试在性能关键的代码中完全避免它们。

注意:从 Xcode 12/Swift 5.3 开始仍然如此

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Swift 弱引用比强引用慢得多 的相关文章

随机推荐

  • JsonProperty - 使用不同的名称进行反序列化,但使用原始名称进行序列化?

    我正在从 API 检索 JSON 我正在使用 newtonsoft 这是 json net 对吗 将其反序列化为对象列表 有用 不幸的是 我还需要将其作为 JSON 传递给其他人 他们不能直接调用 API 只有我有权访问它 我说不幸的是 因
  • 是否可以从 Groovy 脚本测试步骤运行其他测试用例中的特定测试步骤

    是否可以从 Groovy 脚本测试步骤运行其他测试用例中的特定测试步骤 不知道该怎么做 谢谢 对的 这是可能的 从 groovy 步骤中 您可以访问 testRunner 您可以使用它来访问soapUI 中的其他所有内容 并且可以在另一个测
  • 如何在 .Net Core 3.1 中禁用 OpenId 连接时的 ssl 证书验证?

    我正在尝试在开发环境中使用其 IP 地址连接到开放 ID 机构 显然 在这种情况下 ssl 验证将失败 我想绕过它 到目前为止没有任何运气 我找到了有关该主题的以下答案 在 OpenIdConnectOptions 类中将 RequireH
  • 术语“cmake”未被识别为 cmdlet 的名称

    我正在尝试在 Windows 10 的命令行中使用 CMake 这以前可以工作 但我不知道为什么它不再工作了 我安装了新版本的 CMake 并将路径添加到环境变量中 但是 当尝试在 Powershell 中使用它时 如下所示 cmake G
  • 为什么 GHCi 输入这个语句很奇怪?

    在回答 stackoverflow 上的问题时 我注意到 GHCi 交互式 在let陈述 也就是说 给定代码 import Control Arrow f maximum id gt gt gt fst m l gt length filt
  • 如何在 Spring Security 中设置自定义无效会话策略

    我正在开发一个基于 Spring Boot 1 1 6 Spring Security 3 2 5 等的 Web 应用程序 我正在使用基于 Java 的配置 Configuration EnableWebMvcSecurity public
  • 当格式已知时从字符串解析 JSON 的最快方法

    我想在 Java 中将 String 解析为内部 JSON 对象 或等效对象 平常的图书馆 Gson and Jackson 对于我的需求来说太慢了 根据我的基准 每个字符串到 Json 解析 gt 100us 我知道有稍微快一点的库 但是
  • 使用 ShouldBeEquivalentTo 时如何排除 IEnumerable 中所有项目的属性?

    在我的 NUnit FluentAssertions 测试中 我使用以下代码将从系统返回的复杂对象与参考对象进行比较 response ShouldBeEquivalentTo reference o gt o Excluding x gt
  • 让 JRebel 与“mvn tomcat:run”一起使用

    我想知道当我从命令行执行 mvn tomcat run 时是否有人可以指出让 JRebel 工作的方向 我可以让 JRebel 在我的 IDE IntelliJ IDEA 中工作 但在 IDE 中运行感觉有点笨重 当我运行测试时 我不喜欢在
  • 将终端光标返回到行首并启用换行

    我正在编写一个过滤器 在用于终端输出的管道中 有时需要 覆盖 刚刚发生的行 它的工作原理是将标准输入逐个字符地传递到标准输出 直到 n达到 然后调用特殊行为 我的问题是如何返回到行的开头 我首先想到的是使用 r或 ANSI 序列 033 1
  • SQL Server 存储过程初学者指南 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心以获得指导 请推荐一些带有快速指南的网
  • 什么是 .NET RIA 服务?

    有人可以简单地解释一下吗 它可以与非 Silverlight 客户端一起使用吗 基本上 NET RIA 服务是一个框架 它隐藏了网络管道逻辑以处理有线 RPC 并在客户端和服务器之间桥接代码 您可以将其视为客户端 服务器开发的 RAD 来自
  • 使用正则表达式验证 url [重复]

    这个问题在这里已经有答案了 我已经尝试了所有可能的方法来使用正则表达式进行 url 验证 但我没有得到任何 我需要的是网址可以是这样的 google com http google com https google com http www
  • 使用 Spring Boot 进行客户端证书身份验证

    我需要导入证书才能向 Spring Boot 应用程序中的外部服务发出 http 请求 我如何设置 Spring Boot 才能做到这一点 那里有很多信息 但我发现它们有点令人困惑 似乎我可能只需要创建类似 truststore jks 密
  • 如何获取树结构中节点的所有子节点? SQL查询?

    表 用户 列 用户 ID 姓名 经理 ID rows 1 nilesh 0 2 nikhil 1 3 nitin 2 4 Ruchi 2 如果我提供用户 ID 它应该列出所有向他报告的人员 如果我给 userId 2 它应该返回 3 4 这
  • GCC - 仅在特定函数上启用编译器标志

    在我正在从事的一个项目中 我正在尝试优化的大文件中有一个四重嵌套的 for 循环 我认为这会受益于使用 funroll all loops 展开的编译器 但是 当我将此标志添加到编译器时 它会展开文件其余部分的其他循环 并使整个程序运行得更
  • 如何查看节点安装的软件包版本?

    我正在调整 Apache 食谱以与 2 4 Apache 一起使用 Opscode Cookbook 目前失败 因为它正在生成带有 LockFile 关键字的 conf 文件 该关键字被排除在 Apache 2 4 关键字列表之外 我想制定
  • 哪些 C# 类型名称很特殊?

    在什么输入下是特殊名称返回真 根据我的简短研究 我发现属性访问器和运算符重载具有特殊名称 以及名称中包含下划线的任何类型 谁能给我完整描述类型名称特殊的情况 ECMA 335 中发布的 CLI 规范是此类信息的不错来源 在文档中搜索rtsp
  • 更改现有 Xcode 项目的 Git 存储库

    我从 Github 克隆了一个废弃的存储库 现在我希望能够将我的更改上传到私有存储库 以便其他人可以与我一起处理这些更改 不幸的是 由于我克隆了它而不是创建分支 所以 Xcode 正在尝试提交原始存储库 有没有办法更改提交的存储库 如果有
  • Swift 弱引用比强引用慢得多

    我正在用 Swift 构建一个物理引擎 在对引擎进行了一些最近的添加并运行基准测试后 我注意到性能大幅下降 例如 在下面的屏幕截图中 您可以看到 FPS 如何从 60 FPS 下降到 3 FPS FPS 位于右下角 最终 我将问题追溯到一行