【问题标题】:Swift Array Get performance optimizationSwift Array 获得性能优化
【发布时间】:2021-06-14 00:04:11
【问题描述】:

我有以下代码。它包含需要尽可能快的getPointAndPos 函数:

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)
    }
}

但是,由于 Swift 内存管理,它一点也不快。我曾经使用字典,但情况更糟。该功能在应用程序中被大量使用,因此它是现在的主要瓶颈。以下是getPointAndPos 函数的分析结果:

如您所见,从数组中获取一个项目大约需要 4.5 秒,这太疯狂了。我尝试遵循我能找到的所有性能优化技术,即:

  • 使用数组代替字典
  • 使用简单类型作为数组元素(在我的例子中是结构)

它有所帮助,但还不够。考虑到在添加元素后我不会从数组中更改元素,有没有办法进一步优化它?

更新 #1:

按照建议,我用 [PointAndPosition] 替换了 [Point] 数组并删除了可选项,这使代码速度提高了 6 倍。另外,根据要求提供使用getPointAndPos函数的代码:

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
}

当前的实现要快得多,但理想情况下我需要让它比现在快 30 倍。不确定它是否可能。这是最新的分析结果:

【问题讨论】:

  • 刚试过。它增加了性能下降。看起来更好,但速度较慢。

标签: arrays swift performance


【解决方案1】:

我怀疑您正在创建一个 PointAndPosition,然后立即将其丢弃。这就是会造成大量内存流失的事情。或者您正在创建大量重复的 PointAndPosition 值。

首先确保这是在发布模式下构建并进行了优化。 ARC 在优化后通常可以删除很多不必要的保留和释放。

如果getPointAndPos必须尽可能快,那么数据应该以它想要的形式存储,即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]
    }
}

我会更进一步,将getPointAndPos 简化为:

    func getPointAndPos(pos: Int) -> PointAndPosition {
        points[pos]
    }

如果这对性能至关重要,那么应该已经完成​​了边界检查,并且您不需要在此处使用 Optional。

我也会对调用它的代码非常感兴趣。这可能比这段代码更重要。您拨打getPointAndPos 的次数可能超出您的需要。 (虽然摆脱结构创建会降低它的重要性。)

【讨论】:

  • 根据要求,提供了更新中使用getPointAndPos函数的代码,并添加了建议的优化结果。非常感谢!它使代码更快。如果您能建议如何进一步优化它,我会很高兴。感谢您的帮助!
  • 这段代码应该没问题。进一步优化它取决于checkPointincrementFunc 的性质以及您正在谈论的点数。例如,可能有更好的算法来实现checkPoint 或预先计算部分或全部。该算法可能适用于 Accelerate 方法以并行执行更多检查。但是,如果它对您的目的来说足够快,我在这里看不到任何危险信号。可能有帮助的一件事是重新考虑通过incrementFunc,如果它总是类似于$0 + n。在这种情况下,只需传递 n 并内联数学。
  • 函数/闭包调用如果真的发生的话会很昂贵。但是编译器经常内联一些东西,所以函数调用不会发生,所以你必须在发布版本上进行测试,看看发生了什么。但是,如果我必须为性能关键代码选择一件事:组织您的数据,以便您的算法变得微不足道。明智的(有时是聪明的)数据组织可能是高性能代码中最重要的工具。
  • 增量函数很简单,要么是{v in return v - 1}要么是{v in return v + 1}checkPoint 函数也很简单 return pp.pnt.x - n == pnt.x || pp.pnt.x + n == pnt.x || pp.pnt.y - n == pnt.y || pp.pnt.y + n == pnt.y。数组中的点数约为 1000,但在极少数情况下可能会更多。
  • 1000 非常小,所以除非你经常调用它,否则可能无关紧要,但摆脱增量函数会更快(更清晰),只需使用 curPos &gt; limit决定增加的方向。
猜你喜欢
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-20
相关资源
最近更新 更多