Reputation: 449
I have a following code. It contains getPointAndPos
function that needs to be as fast as possible:
struct Point {
let x: Int
let y: Int
}
struct PointAndPosition {
let pnt: Point
let pos: Int
}
class Elements {
var points: [Point]
init(points: [Point]) {
self.points = points
}
func addPoint(x: Int, y: Int) {
points.append(Point(x: x, y: y))
}
func getPointAndPos(pos: Int) -> PointAndPosition? {
guard pos >= 0 && points.count > pos else {
return nil
}
return PointAndPosition(pnt: points[pos], pos: pos)
}
}
However, due to Swift memory management it is not fast at all. I used to use dictionary, but it was even worse. This function is heavily used in the application, so it is the main bottleneck now. Here are the profiling results for getPointAndPos
function:
As you can see it takes ~4.5 seconds to get an item from array, which is crazy. I tried to follow all performance optimization techniques that I could find, namely:
It helped, but it is not enough. Is there a way to optimize it even further considering that I do not change elements from array after they are added?
UPDATE #1:
As suggested I replaced [Point]
array with [PointAndPosition]
one and removed optionals, which made the code 6 times faster. Also, as requested providing the code which uses getPointAndPos
function:
private func findPoint(el: Elements, point: PointAndPosition, curPos: Int, limit: Int, halfLevel: Int, incrementFunc: (Int) -> Int) -> PointAndPosition? {
guard curPos >= 0 && curPos < el.points.count else {
return nil
}
// get and check point here
var next = curPos
while true {
let pnt = el.getPointAndPos(pos: next)
if checkPoint(pp: point, pnt: pnt, halfLevel: halfLevel) {
return pnt
} else {
next = incrementFunc(next)
if (next != limit) {
continue //then findPoint next limit incrementFunc
}
break
}
}
return nil
}
Current implementation is much faster, but ideally I need to make it 30 times faster than it is now. Not sure if it is even possible. Here is the latest profiling result:
Upvotes: 0
Views: 630
Reputation: 299355
I suspect you're creating a PointAndPosition and then immediately throwing it away. That's the thing that's going to create a lot of memory churn. Or you're creating a lot of duplicate PointAndPosition values.
First make sure that this is being built in Release mode with optimizations. ARC can often remove a lot of unnecessary retains and releases when optimized.
If getPointAndPos
has to be as fast as possible, then the data should be stored in the form it wants, which is an array of PointAndPosition
:
class Elements {
var points: [PointAndPosition]
init(points: [Point]) {
self.points = points.enumerated().map { PointAndPosition(pnt: $0.element, pos: $0.offset) }
}
func addPoint(x: Int, y: Int) {
points.append(PointAndPosition(pnt: Point(x: x, y: y), pos: points.endIndex))
}
func getPointAndPos(pos: Int) -> PointAndPosition? {
guard pos >= 0 && points.count > pos else {
return nil
}
return points[pos]
}
}
I'd take this a step further and reduce getPointAndPos
to this:
func getPointAndPos(pos: Int) -> PointAndPosition {
points[pos]
}
If this is performance critical, then bounds checks should already have been done, and you shouldn't need an Optional here.
I'd also be very interested in the code that calls this. That may be more the issue than this code. It's possible you're calling getPointAndPos
more often than you need to. (Though getting rid of the struct creation will make that less important.)
Upvotes: 3