使用 Time Profiler 查看关键函数调用耗时情况,从而分析和解决问题

swift 复制代码
1.59 s   15.4%	1.31 s	 	    closure #6 in LineChartView.generateSingleLineChart(from:)
579.00 ms    5.6%	490.00 ms	 	    closure #6 in LineChartView.generateGroupLineChart(from:)
524.00 ms    5.0%	423.00 ms	 	    closure #1 in closure #4 in LineChartView.generateLineChart(from:)
269.00 ms    2.6%	221.00 ms	 	    closure #1 in closure #6 in LineChartView.generateOtherLineChart(from:)
92.00 ms    0.8%	69.00 ms	 	    closure #1 in closure #1 in closure #4 in LineChartView.ChartSectionView.body.getter


1.06 s   21.2%	884.00 ms	 	    closure #6 in LineChartView.generateSingleLineChart(from:)

	let chartContent = ForEach(data) { model in
            // LineMark for data
226 ms            LineMark(
43 ms                x: .value("Time", model.timestamp),
49 ms                y: .value("Value", model.value)
            )
166 ms            .foregroundStyle(viewModel.getLineColor(for: model.name))
561 ms            .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
        }

325.00 ms    6.5%	259.00 ms	 	    closure #1 in closure #4 in LineChartView.generateLineChart(from:)
			return Chart {
            ForEach(data) { model in
102 ms            LineMark(
18 ms               x: .value("Time", model.timestamp),
18 ms                y: .value("Value", model.value)
                )
185 ms              .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
            }
        }

306.00 ms    6.1%	261.00 ms	 	    closure #6 in LineChartView.generateGroupLineChart(from:)

let chartContent = ForEach(data) { model in
            // LineMark for data
58 ms             LineMark(
10 ms               x: .value("Time", model.timestamp),
6 ms               y: .value("Value", model.value)
            )
67 ms            .foregroundStyle(by: .value("Field", model.name))
158 ms              .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
        }
144.00 ms    2.8%	123.00 ms	 	    closure #1 in closure #6 in LineChartView.generateOtherLineChart(from:)

return Chart {
            ForEach(data) { model in
25 ms             LineMark(
7 ms             x: .value("Time", model.timestamp),
6 ms             y: .value("Value", model.value)
                )
23 ms            .foregroundStyle(model.color)
77 ms             .lineStyle(StrokeStyle(lineWidth: chartLineWidth, lineCap: .round, lineJoin: .round))
            }
        }
42.00 ms    0.8%	42.00 ms	 	    initializeWithCopy for LineChartData

1. 分析 Time Profiler 的耗时情况

  • 闭包内部耗时较高

    • generateSingleLineChart 中,ForEach 循环内部的闭包花费了 884 毫秒,其中 .lineStyle(...) 调用占了 561 毫秒,说明对每个数据点的样式计算非常耗时。
    • 同理,generateLineChartgenerateGroupLineChart 中,也分别花费了 259 毫秒和 261 毫秒。这表明在遍历数据并生成 LineMark 时,SwiftUI 在计算各个修饰符(如 lineStyleforegroundStyle)的过程中开销较大。
  • 重复计算和视图重构

    • 每次调用这些生成图表的函数时,都需要重新计算 x 轴、y 轴的最小值、最大值、刻度值等,而且对每个数据点都要构建一个新的 LineMark。如果数据点较多,这些计算和视图构造操作叠加起来就会变得非常耗时。
  • 样式计算问题

    • 例如在 generateSingleLineChart 中,对每个数据点调用 viewModel.getLineColor(for:) 和创建 StrokeStyle 都消耗了大量时间。这提示我们,样式的计算和生成可能没有复用,每次都在重新构建。

  • 重绘整个视图
    • 当从 LineChartView 跳转到子页面后返回,SwiftUI 会重新调用 body 生成视图。即使数据没有改变,所有的闭包(如 generateSingleLineChartgenerateGroupLineChartgenerateLineChart 等)都会被重新执行,导致整个图表视图重新计算和绘制。
  • 视图重构导致的重复计算
    • 由于 SwiftUI 的声明式特性,每次视图出现时都会重新计算其内部状态,进而触发大量重复的计算操作(比如对时间范围、轴值和样式的计算),所以返回时会明显卡顿,耗时达到 56 秒。

3. 优化思路和解决方案

针对上述问题,可以从以下几个方面考虑优化:

a. 数据和样式的预计算与缓存
  • 提前计算图表数据范围和样式
    • x 轴和 y 轴的计算、刻度值生成、以及各个数据点对应的样式等计算移到 viewModel 中,提前计算好并缓存。这样在视图构建时,只需直接使用缓存结果,避免重复计算。
  • 缓存 StrokeStyle 和颜色
    • 如果 chartLineWidthlineCaplineJoin 等参数不会变化,可以将生成的 StrokeStyle 定义为常量,避免在 ForEach 内部重复创建。
b. 减少不必要的视图重构
  • 局部视图拆分和懒加载

    • 将复杂且耗时的图表部分拆分成独立的子视图,利用 SwiftUI@StateObject@ObservedObject 缓存数据,防止因父视图重新构建而全部重绘。
    • 如果页面上图表较多,可以考虑使用 LazyVStack 替换 VStack,使得只有当视图真正需要显示时才进行计算和绘制。
  • 视图缓存策略

    • 当数据不频繁变化时,可以考虑在视图中利用 .drawingGroup() 或其他缓存手段,将渲染结果缓存成图像,在下一次展示时直接使用缓存图像而不是重新绘制所有的线条。
c. 关注 SwiftUI 的绘图和布局机制
  • 审查 Chart 的内部实现
    • SwiftUIChart 组件可能在处理大量数据点时没有做到最优。如果可能,考虑对数据进行适当的抽样或者合并,减少绘制的元素数量。
  • 分析 View 重构触发机制
    • 检查是否存在不必要的状态更新或绑定,导致整个 LineChartView 被频繁重构。如果能做到局部状态隔离,只让真正需要更新的部分重绘,也会大大降低耗时。

总结

  1. 问题定位

    • 耗时主要在 ForEach 循环中生成各个 LineMark 的过程中,特别是 .lineStyle 的计算,以及在视图重构时重复计算各项轴值和样式。
    • 导航返回时,因为整个视图重新构建,所有的图表闭包都会再次执行,造成明显卡顿。
  2. 优化方案

    • 预计算和缓存 :在 viewModel 中提前计算好各个图表需要的数据、刻度、样式等,避免在视图中重复计算。
    • 减少重复视图构建 :将耗时较大的图表部分拆分为独立的子视图,并使用懒加载、缓存视图或 .drawingGroup() 技术来减少重绘。
    • 调整数据量:如果数据点非常多,可以考虑做数据抽样或聚合,降低绘图时的计算量。
相关推荐
天若有情6739 小时前
03_性能优化:让软件呼吸更顺畅
计算机·性能优化·软件·发展
双力臂4049 小时前
MyBatis动态SQL进阶:复杂查询与性能优化实战
java·sql·性能优化·mybatis
我就是全世界10 小时前
TensorRT-LLM:大模型推理加速的核心技术与实践优势
人工智能·机器学习·性能优化·大模型·tensorrt-llm
骑着王八撵玉兔16 小时前
【性能优化与架构调优(二)】高性能数据库设计与优化
数据库·性能优化·架构
杰尼橙子17 小时前
DPDK基础架构解析:EAL环境抽象层的设计与实现
网络协议·性能优化
西岭千秋雪_1 天前
Redis性能优化
数据库·redis·笔记·学习·缓存·性能优化
ItJavawfc1 天前
RK-Android11-性能优化-限制App内存上限默认512m
性能优化·heapsize·heapgrowthlimit·虚拟机参数·内存上限
瓜子三百克1 天前
七、性能优化
flutter·性能优化
脑袋大大的1 天前
JavaScript 性能优化实战:减少 DOM 操作引发的重排与重绘
开发语言·javascript·性能优化
程序员岳焱1 天前
Java 与 MySQL 性能优化:Java 实现百万数据分批次插入的最佳实践
后端·mysql·性能优化