title: Swift Weak Reference Much Slower than Strong Reference
body: <p>I'm building a physics engine in Swift. After making some recent additions to the engine and running the benchmarking tests I noticed the performance was drastically slower. For example, in the screenshots below you can see how the FPS dropped from 60 to 3 FPS (FPS is in the bottom-right corner). Eventually, I traced the problem down to just a single line of code:</p>
  <pre><code>final class Shape {
      ...
      weak var body: Body! // This guy
      ...
  }
  </code></pre>
  <img src="https://i.stack.imgur.com/qlJh2.png" width="400" />
  <img src="https://i.stack.imgur.com/erq33.png" width="400" />
  <p>At some point in my additions I added a weak reference from the <code>Shape</code> class to the <code>Body</code> class. This is to prevent a strong reference cycle, as <code>Body</code> also has a strong reference to <code>Shape</code>.</p>
  <p>Unfortunately, it appears weak references have a significant overhead (I suppose the extra steps in nulling it out). I decided to investigate this further by building a massively simplified version of the physics engine below and benchmarking different reference types.</p>
  <hr />
  <pre><code>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 ..&lt; 8 {
              self.vertices.append( CGPoint(x:CGFloat.random(in: -10...10), y:CGFloat.random(in: -10...10) ))
          }
      }
  }
  
  var bodies: [Body] = []
  for _ in 0 ..&lt; 1000 {
      bodies.append(Body(shape: Shape()))
  }
  
  var pairs: [(Shape,Shape)] = []
  for i in 0 ..&lt; bodies.count {
      let a = bodies[i]
      for j in i + 1 ..&lt; 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)
  </code></pre>
  <hr />
  <h2>Results</h2>
  <p>Below are the benchmark times for each reference type. In each test, the <code>body</code> reference on the <code>Shape</code> class was changed. The code was built using release mode [-O] with Swift 5.1 targeting macOS 10.15.</p>
  <p><code>weak var body: Body!</code>: 0.1886 s</p>
  <p><code>var body: Body!</code>: 0.0167 s</p>
  <p><code>unowned body: Body!</code>: 0.0942 s</p>
  <p>You can see using a strong reference in the computation above instead of a weak reference results in over 10x faster performance. Using <code>unowned</code> helps, but unfortunately it is still 5x slower. When running the code through the profiler, there appear to be additional runtime checks being performed resulting in much overhead.</p>
  <p><strong>So the question is</strong>, what are my options for having a simple back pointer to Body without incurring this ARC overhead. And furthermore why does this overhead seem so extreme? I suppose I could keep the strong reference cycle and break it manually. But I'm wondering if there is a better alternative?</p>
  <p><strong>Update:</strong>
  Based on the answer, here is the result for<br />
  <code>unowned(unsafe) var body: Body!</code>: 0.0160 s</p>
  <p><strong>Update2:</strong>
  As of Swift 5.2 (Xcode 11.4), I have noticed that unowned(unsafe) has much more overhead. Here is the result now for
  <code>unowned(unsafe) var body: Body!</code>: 0.0804 s</p>
  <p>Note: This is still true as of Xcode 12/Swift 5.3</p>