I'm in the process of porting a C++ library that relies heavily on the use of a doubly-linked list to Swift and have run into a performance issue relating to swift_release(swift::HeapObject*) and swift_retain(swift::HeapObject*) overhead. Specifically, Time Profiler reports a spend of around 770ms in my main library function, but reports that 561ms of that time is spent in _swift_release/retain.
A few notes on the code included below: the library in question is responsible for polygon tessellation. The port has tried to remain as faithful to the original library as possible for clarity, and as such isn't particularly idiomatic Swift code.
Here's my linked list Node class:
final class Node : Equatable {
// vertex index in coord array
var i:Int
// vertex coordinates
var x:Scalar
var y:Scalar
// z-order curve value
var z:Scalar? = nil
// previous and next nodes in z-order
weak var prevZ:Node? = nil
var nextZ:Node? = nil
// previous and next vertex nodes in a polygon ring
weak var prev:Node? = nil
var next:Node? = nil
// indicates whether this is a steiner point
var steiner:Bool = false
init(i:Int, x:Scalar, y:Scalar) {
self.i = i
self.x = x
self.y = y
}
static func == (lhs: Node, rhs: Node) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
}
And here's an example of a library function that incurs a strong retain/release overhead. The code is fairly heavy for demonstration purposes, but it appears from watching a few Swift performance related WWDC sessions that a lot of this overhead comes from the reference semantics related to the use of Optional values for the next/prev variables (and iterating over them), but I'm not sure how this can be avoided in Swift.
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
private func sortLinked(_ _list:Node) -> Node {
var list:Node? = _list
var tail:Node?
var e:Node?
var p:Node?
var q:Node?
var qSize:Int = 0
var pSize:Int = 0
var inSize:Int = 1
var numMerges:Int = 0
repeat {
p = list
list = nil
tail = nil
numMerges = 0
while p != nil {
numMerges += 1
q = p
pSize = 0
for _ in 0 ..< inSize {
pSize += 1
q = q!.nextZ
if q == nil { break }
}
qSize = inSize
while pSize > 0 || (qSize > 0 && q != nil) {
if (pSize != 0 && (qSize == 0 || q == nil || p!.z! <= q!.z!)) {
e = p;
p = p!.nextZ;
pSize-=1;
} else {
e = q;
q = q!.nextZ;
qSize-=1;
}
if tail != nil {
tail!.nextZ = e
} else {
list = e
}
e!.prevZ = tail
tail = e!
}
p = q
}
tail?.nextZ = nil
inSize *= 2
} while numMerges > 1
return list!
}
Hopefully someone here can point me in the right direction with regards to how to improve release/retain when iterating over the linked list. Were this C or C++ such pointer manipulation would essentially be free (and admittedly more dangerous). How can I achieve similar performance in Swift?