我正在使用Swift构建物理引擎。在对引擎进行一些最近的添加并运行基准测试后,我注意到性能显着变慢。例如,在下面的截图中,您可以看到FPS从60下降到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)
结果
以下是每种引用类型的基准时间。在每个测试中,更改了Shape
类上的body
引用。使用Swift 5.1以目标macOS 10.15为目标,在发布模式[-O]下构建代码。
weak var body: Body!
: 0.1886秒
var body: Body!
: 0.0167秒
unowned body: Body!
: 0.0942秒
可以看出,在计算中使用强引用而不是弱引用会导致性能提高超过10倍。使用unowned
有所帮助,但不幸的是它仍然比使用强引用慢5倍。当通过分析器运行代码时,似乎执行了额外的运行时检查,导致开销很大。
所以问题是,在不产生这种ARC开销的情况下,有哪些选项可用于拥有一个简单的回指针到Body?此外,为什么这种开销看起来如此极端?我想我可以保留强引用循环并手动打破它。但我想知道是否有更好的选择?
更新:
根据答案,以下是结果
unowned(unsafe) var body: Body!
: 0.0160秒
更新2:
从Swift 5.2(Xcode 11.4)开始,我注意到unowned(unsafe)
有更多的开销。现在这是结果
unowned(unsafe) var body: Body!
: 0.0804秒
Struct
,最好是带有自定义的写时复制? - Kamil.S