首页 > 解决方案 > 如何优化快速代码以在可以滚动和缩放的图形中实时绘制大型数据集

问题描述

我正在快速编写自己的代码来绘制图表以显示大型数据集。(不使用现有框架的原因是我想使用对数和线性视图,以及用户与图形的交互)。我需要帮助来加快我的代码速度,以尝试让应用程序感觉响应,因为它目前太慢了。

我将我的数据集传递到我的幅度图类(UIView)中,并计算数据线(divLine)的坐标,并使用以下函数将它们保存为 UIBezierPath:

func calcDataCoOrdinates(frequencies: [Float], Mag: [Float]) -> UIBezierPath {
    let divLine = UIBezierPath()
    let graphWidth:Int = width - dBLabelSpace
    let graphHeight:Int = height - freqLabelSpace
    let yRange = Float(yMax-yMin)

    //Limit Data to the Values being displayed
    let indexOfGreaterThanXMin = frequencies.firstIndex(where: {$0 > xMin})
    let indexLessThanXMax = frequencies.lastIndex(where: {$0 < xMax})
    let frequencyRange = frequencies[indexOfGreaterThanXMin!..<indexLessThanXMax!]
    let MagnitudeRange = Mag[indexOfGreaterThanXMin!..<indexLessThanXMax!]

    let graphRatio = (yMax-yMin)/Float(graphHeight)
    let logX = vForce.log10(frequencyRange)
    let logRange = log10(Double(xMax))-log10(Double(xMin));
    let graphDivideByLog = Float(graphWidth) / Float(logRange)
    let xMinArray:[Float] = Array(repeating: Float(log10(xMin)), count: logX.count)
    let x5 = vDSP.multiply(subtraction: (a: logX, b: xMinArray), graphDivideByLog)
    let x6 = vDSP.add(Float(dBLabelSpace + 10), x5)
    let yMaxArray = Array(repeating:Float(yMax), count: MagnitudeRange.count)
    let y2 = vDSP.subtract(yMaxArray, MagnitudeRange)
    let y3 = vDSP.divide(y2, Float(graphRatio))

    var allPoints:[CGPoint]=[]
    for i in 0..<x6.count{
        let value = CGPoint.init(x: Double(x6[i]), y: Double(y3[i]))
        allPoints.append(value)
    }

    divLine.removeAllPoints()
    divLine.move(to: allPoints[0])
    for i  in 1 ..< allPoints.count {
        divLine.addLine(to: allPoints[i])
    }

    return divLine
}

我没有直接从触发它的 UI 元素调用 setNeedsDisplay,而是调用 redrawGraph()。这会在后台线程中调用 calcDataCoOrdinates 函数,然后在计算完所有坐标后,我会在主线程上调用 setNeedsDisplay。

func redrawGraph(){
    width = Int(self.frame.width);
    height = Int(self.frame.height);

    magnitudeQueue.sync{
        if mag1.isEmpty == false {
            self.data1BezierPath = self.calcData1CoOrdinates(frequencies: freq1, Mag: mag1)
        }
        if mag2.isEmpty == false {
            self.data2BezierPath = self.calcData1CoOrdinates(frequencies: freq2, Mag: mag2)
        }
        if magSum.isEmpty == false {
            self.dataSumBezierPath = self.calcData1CoOrdinates(frequencies: freqSum, Mag: magSum)
        }            
        DispatchQueue.main.async {
            self.setNeedsDisplay()
        }
    }

最后贴出实际绘图功能的代码:

override func draw(_ rect: CGRect) {
    self.layer.sublayers = nil
    drawData(Color: graphData1Color, layer: in1Layer, lineThickness: Float(graphDataLineWidth), line:data1BezierPath)
    drawData(Color: graphData2Color, layer: in2Layer, lineThickness: Float(graphDataLineWidth), line:data2BezierPath)
    drawData(Color: sumColor, layer: in3Layer, lineThickness: Float(sumLineThickness), line: dataSumBezierPath)
}

func drawData(Color: UIColor, layer: CAShapeLayer, lineThickness:Float, line:UIBezierPath){
    layer.removeFromSuperlayer()
    layer.path = nil
    Color.set()
    line.lineWidth = CGFloat(lineThickness)
    layer.strokeColor = Color.cgColor
    layer.fillColor = UIColor.clear.cgColor
    layer.path = line.cgPath
    layer.lineWidth = CGFloat(lineThickness)
    layer.contentsScale = UIScreen.main.scale
    let mask = CAShapeLayer()
    mask.contentsScale = UIScreen.main.scale
    mask.path = bpath?.cgPath
    layer.mask = mask
    self.layer.addSublayer(layer)

}

此代码可以很好地计算三个数据集并在图表上查看它们。我已经排除了计算背景层(网格线)的代码,但这只是为了简化代码。

我得到的问题是,要尝试和处理“实时”交互性需要做很多事情。我认为我们需要大约 30 fps 的速度来感觉响应,这总共提供了大约 33 毫秒的处理和渲染时间。

现在,如果我说 32k 个数据点,目前正在测量这些数据点,那么 calcDataCoOrdinates 的三遍中的每一遍都可以在它们之间进行。22-36ms,总计约90ms。1/10 秒非常滞后,所以我的问题是如何加快速度?如果我测量实际的绘图过程,这目前非常低(1-2ms),这让我相信我的 calcDataCoOrdinates 函数需要改进。

我尝试了几种方法,包括使用 Douglas-Peucker 算法来减少数据点的数量,但是这是一个很大的时间成本(+100 毫秒)。

我尝试跳过重叠像素的数据点

我尝试尽可能多地使用加速框架,该函数的上半部分在大约 10 毫秒内运行,但下半部分将坐标放入 CGPoint 数组,并迭代生成 UIBezierPath 需要 20 毫秒。我尝试使用 CGPoints 预初始化 allPoints 数组,认为这会节省一些时间,但这仍然需要很长时间。

能否计算得更多。有效还是我达到了我的应用程序的限制?

标签: swiftgraphicsdrawingcashapelayeraccelerate

解决方案


推荐阅读