使用 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 毫秒,说明对每个数据点的样式计算非常耗时。
    • 同理,generateLineChart 和 generateGroupLineChart 中,也分别花费了 259 毫秒和 261 毫秒。这表明在遍历数据并生成 LineMark 时,SwiftUI 在计算各个修饰符(如 lineStyle、foregroundStyle)的过程中开销较大。
  • 重复计算和视图重构

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

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

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

3. 优化思路和解决方案

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

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

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

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

总结

  1. 问题定位:

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

    • 预计算和缓存 :在 viewModel 中提前计算好各个图表需要的数据、刻度、样式等,避免在视图中重复计算。
    • 减少重复视图构建 :将耗时较大的图表部分拆分为独立的子视图,并使用懒加载、缓存视图或 .drawingGroup() 技术来减少重绘。
    • 调整数据量:如果数据点非常多,可以考虑做数据抽样或聚合,降低绘图时的计算量。
相关推荐
贾伟康16 小时前
HarmonyOS API 26 分块 3DGS 实战:相机驱动、瓦片回调与并发去重
性能优化·harmonyos·arkts·3dgs·spatial recon kit
李纲明2 天前
WordPress 定时发布不准点或备份/邮件从不跑:先分清 WP-Cron 触发与 DISABLE_WP_CRON,再查回环与系统 Cron
运维·性能优化·wordpress·定时任务·devdiy·wp-cron
Patrick在香港2 天前
一个斜杠,把毫秒变成十几秒
android·数据库·python·性能优化·正则表达式·回溯·redos
Sayai3 天前
Flink 日志解析任务性能优化实战:正则/Grok 预编译、Guava 缓存权重陷阱与 ES 写入隔离
java·大数据·性能优化·flink·实时计算
raindayinrain3 天前
c++高性能--原子操作,无锁编程,操作顺序
开发语言·c++·性能优化
洞见前行3 天前
点击图标到内容出现,你的 App 在这几秒里做了什么:Android 冷启动全链路优化实战
android·性能优化·android studio
shenlongproxy3 天前
代理IP速度测试怎么做:响应耗时分级与吞吐基准
性能优化
半杯咖啡半行码4 天前
QML进阶实战(二):异步网络、性能优化与工程化落地
网络·qt·性能优化·qml
m0_564876844 天前
GPU编程与性能优化(1)
性能优化
胖纸不争4 天前
EnergyStarZ:让 Windows 后台进程给前台让路的能效管理器(v1.2 发布)
windows·性能优化·c#·win32·ef core