Can you reduce the ARC calls when calling a function on a struct inside an Array?

While doing some benchmarking using Instruments, I noticed that ARC calls to swift_retain and swift_release where accounting for a measurable amount of time in a relatively small function.

The data structure is a tree of Nodes:

// Simplified example of a node.
struct Node {
  var identifier: Int
  var children: Array<Node>
  
  // Recursively gather the value of a property
  // for each node in the tree.
  func reduceIndentifiers(into accumulator: inout Array<Int>) {
    accumulator.append(identifier)
    
    for index in children.indices {
      // Does this line require a retain/relase?
      children[index].reduceIndentifiers(into: &accumulator)
    }
  }
}

// Tree is only a few levels deep but the leaf nodes 
// might contain a few million children.
let rootNode = Node(...)

The method might be called as so:

  var allIdentifiers = Array<Int>()
  rootNode.reduceIndentifiers(into: & allIdentifiers)

Looking at the profile trace, it looks like the call to children[index] requires calls to retain/release. Is that because the subscript notation is actually creating its own "copy" of the node when calling the function and thus the children Array requires reference counting? Is there a way to invoke the function without triggering a call to ARC? Or am I just reading the trace incorrectly?

Instruments Trace:

why not:

for child in children {
  child.reduceIndentifiers(into: &accumulator)
}
or
children.forEach { child in
  child.reduceIndentifiers(into: &accumulator)
}

i'd also try and see if there's any difference of using a global (static) variable for accumulator instead of passing it as an inout parameter.

if nothing else helps then for children elements i'd play with some custom array implementation that works with some fixed storage for small number of elements and switches over to real array when the number of elements grows over a certain threshold like a 100.

In both those examples, it's my understanding that the local child instance will effectively be a copy and thus incur a reference count to the containing array property. In the real application, the Node has numerous properties so using the subscript accessor was one idea to try and cut down on unnecessary copies.

The API for creating a Node is public so it would be nice if callers can always just pass in a standard Swift Array for the children. There might be some benefit in converting that to a custom data structure internally but I'd need to see if the cost to do that conversion is worth it.

this conversion is going to be quick: big arrays are converted instantly, and small arrays conversion is quick because of the small number of elements. you can even make this conversion "lazy".

however, i'd also like to see what others say about reducing ARC traffic in this example with normal arrays.

Terms of Service

Privacy Policy

Cookie Policy