iOS Trace 数据解析
目录
概览
本文档介绍如何解析和分析 Xcode Instruments 生成的 Trace 文件。Trace 文件包含了应用运行时的性能数据,包括 CPU 使用情况、函数调用堆栈、线程信息等。通过解析 Trace 文件,可以深入了解应用的性能特征,定位性能瓶颈。
主要内容:
- 基本概念:理解 Trace 文件中的关键概念,如
all_weight和self_weight - 符号化:将内存地址转换为可读的函数名和代码位置
- 文件格式:了解 Trace 文件的内部结构
- 解析流程:掌握解析 Trace 文件的完整流程
- 数据统计:学习如何正确统计和分析性能数据
一、基本概念
1.1 all_weight 和 self_weight
在 Instruments 的性能数据中,每个函数都有两个重要的时间指标:
- all_weight:包含子函数调用的总执行时间
- 表示函数及其所有子函数的总耗时
-
用于了解函数在整个调用链中的总开销
-
self_weight:函数自身执行时间(不包含子函数调用)
- 表示函数本身的执行时间,不包括调用其他函数的时间
- 用于了解函数本身的性能开销
关系公式:
all_weight = self_weight + sum(子函数的 all_weight)
示例:
假设函数 A() 调用了 B() 和 C():
A() {
// self_weight = 10ms
B(); // all_weight = 20ms
C(); // all_weight = 30ms
}
A 的 all_weight = 10 + 20 + 30 = 60ms
A 的 self_weight = 10ms
使用场景:
- all_weight:用于分析函数在整个调用链中的总开销,适合定位性能瓶颈
- self_weight:用于分析函数本身的性能,适合优化函数内部实现
1.2 Trace 文件结构
Trace 文件是 Instruments 生成的性能数据文件,包含以下核心元素:
- id:所有元素都有一个全局唯一的
id - weight:表示执行时间或权重
- text-address:函数调用地址
- backtrace:调用堆栈信息
- thread:线程信息
二、符号化
符号化是将内存地址转换为可读的函数名和代码位置的过程。这是解析 Trace 文件的关键步骤。
2.1 符号化流程
完整流程:
- 生成模块列表文件:获取所有模块的 UUID 和基本信息
- 使用
atos命令符号化地址:将内存地址转换为函数名和行号 - 生成 Symbols 文件:为后续解析准备符号信息
2.2 查看模块 UUID
每个模块(可执行文件、动态库等)都有一个唯一的 UUID,用于标识和匹配符号文件。
方法一:使用 symbols 命令
'/Applications/Xcode.app/Contents/Developer/usr/bin/symbols' -uuid 'WeMeetApp.app.dSYM/Contents/Resources/DWARF/WeMeetApp'
方法二:使用 dwarfdump 命令
xcrun dwarfdump --uuid *.dSYM
输出示例:
UUID: 7C6E3623-FD50-3603-82E3-7F4F29B5DF76 (arm64) WeMeetApp.app.dSYM/Contents/Resources/DWARF/WeMeetApp
2.3 获取模块信息
获取模块的基地址和大小等信息,用于地址计算。
'/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool' -arch arm64 -l '/Users/mjzheng/Library/Developer/Xcode/iOS DeviceSupport/14.1 (18A8395)/Symbols/usr/lib/system/libsystem_pthread.dylib'
关键信息:
- 基地址(Load Address):模块在内存中的起始地址
- 大小(Size):模块的大小
- 架构(Architecture):arm64、armv7 等
2.4 生成 Symbols 文件
生成符号文件包,用于后续的符号化过程。
xcrun symbols -arch all -symbolsPackageDir symbols WeMeetApp.app.dSYM/Contents/Resources/DWARF/WeMeetApp
参数说明:
-arch all:生成所有架构的符号文件-symbolsPackageDir symbols:指定符号文件输出目录- 最后一个参数:dSYM 文件的路径
输出:
会在 symbols 目录下生成以 UUID 命名的 .symbols 文件,例如:
symbols/7C6E3623-FD50-3603-82E3-7F4F29B5DF76.symbols
参考:Google 搜索关键词:xcrun symbols
2.5 使用 atos 符号化
使用 atos 命令将内存地址转换为函数名和行号。
# 基本用法
atos -o <binary> -l <load_address> <address>
# 示例
atos -o WeMeetApp.app/WeMeetApp -l 0x100000000 0x100001234
参数说明:
-o:指定可执行文件或 dSYM 文件-l:指定模块的加载地址(Load Address)- 最后一个参数:要符号化的内存地址
输出示例:
-[ViewController viewDidLoad] (in WeMeetApp) (ViewController.m:45)
三、Trace 文件格式
3.1 Trace 文件结构
Trace 文件(.trace)是 Instruments 生成的二进制文件,可以通过 xctrace 命令导出为 XML 格式进行分析。
导出 XML:
xcrun xctrace export --input 34.trace --xpath '/trace-toc/run[@number="1"]/data/table[@schema="time-profile"]' --output 34.xml
3.2 Symbols 文件格式
Symbols 文件以模块的 UUID 命名,格式为:
<UUID>.symbols
示例:
7C6E3623-FD50-3603-82E3-7F4F29B5DF76.symbols
前缀为 UUID,用于标识模块。
3.3 XML 格式说明
导出的 XML 文件包含以下核心元素:
基本元素
- 所有元素都有一个全局唯一
id:用于建立元素之间的关联关系
weight 元素
weight fmt:表示执行时间- 单位通常是微秒(microseconds)
- 用于计算函数或堆栈的执行时间
text-address 元素
- 表示函数调用链:一个地址对应一个函数调用
- 根函数在最末尾:调用链的顺序是从调用者到被调用者
- 多个
text-address组成调用堆栈
示例:
<text-address id="123">0x100001234</text-address>
<text-address id="124">0x100002345</text-address>
backtrace 元素
- 一个
backtrace表示一个调用堆栈 - 包含多个
text-address:按照从上到下的顺序组成调用堆栈 - 关联一个
weight id:表示该堆栈的执行时间
结构示例:
<backtrace id="456">
<text-address id="123"/>
<text-address id="124"/>
<weight id="789"/>
</backtrace>
调用堆栈顺序:
函数A (顶层,调用者)
└─ 函数B
└─ 函数C (底层,被调用者)
四、解析流程
4.1 解析步骤
Trace 文件 id 是唯一的,text-addresses 顶层地址在最后面,一个线程有多个 backtrace id。
完整解析步骤:
- 获取
id->item的字典 - 建立所有元素的映射关系
-
通过
id快速查找对应的元素 -
建立
thread_id->backtrace id数组的映射 - 将每个线程的所有
backtrace组织起来 -
相同
backtrace id的权重相加,得到backtrace的总权重 -
解析
backtrace id的调用地址 - 遍历
backtrace中的text-address -
将地址转换为函数名(使用符号化工具)
-
获取线程树
- 遍历线程的所有
backtrace - 所有
backtrace的权重相加得到线程的总权重 - 层次遍历
backtrace里面的调用地址,同一层相同地址进行合并
4.2 数据结构映射
关键数据结构:
# id -> item 映射
id_to_item = {
"123": text_address_element,
"456": backtrace_element,
"789": weight_element,
...
}
# thread_id -> backtrace_ids 映射
thread_to_backtraces = {
"thread_1": ["backtrace_1", "backtrace_2", ...],
"thread_2": ["backtrace_3", "backtrace_4", ...],
...
}
# backtrace_id -> weight 映射
backtrace_weights = {
"backtrace_1": 1000, # 微秒
"backtrace_2": 2000,
...
}
4.3 线程树构建
构建过程:
-
收集线程的所有 backtrace
python
thread_backtraces = thread_to_backtraces[thread_id] -
计算线程总权重
python
thread_total_weight = sum(backtrace_weights[bt_id] for bt_id in thread_backtraces) -
构建调用树
- 遍历每个
backtrace的text-address序列 - 从顶层(最后一个地址)到底层(第一个地址)构建树结构
- 同一层的相同地址进行合并,权重累加
示例:
假设有两个 backtrace:
Backtrace 1:
A -> B -> C (weight: 100)
Backtrace 2:
A -> B -> D (weight: 200)
构建的调用树:
A (weight: 300)
└─ B (weight: 300)
├─ C (weight: 100)
└─ D (weight: 200)
五、数据统计方法
5.1 平均统计 vs 加权平均
在性能数据分析中,选择合适的统计方法非常重要。
平均统计
公式:平均值 = sum(所有值) / 数量
特点:
– 简单直观
– 易受极端值(bad case)影响
– 可能掩盖性能问题
示例:
假设 99 个用户 CPU 使用率为 1%,1 个用户 CPU 使用率为 99%:
平均统计 = (99 × 1 + 1 × 99) / 100 = 1.98%
结果看起来正常,但实际上有一个用户出现了严重的性能问题。
加权平均
公式:加权平均 = sum(值 × 权重) / sum(权重)
特点:
– 可以根据重要性分配权重
– 仍然可能受极端值影响
– 需要合理设置权重
示例:
加权平均 = (1 × 0.99 + 99 × 0.01) = 1.98%
如果权重设置不合理,仍然可能掩盖问题。
5.2 分位数统计
分位数统计是性能监控中更可靠的方法。
常用分位数
- P50(中位数):50% 的数据小于等于该值
- P95:95% 的数据小于等于该值
- P99:99% 的数据小于等于该值
- P99.9:99.9% 的数据小于等于该值
优势
- 不受极端值影响:P95、P99 等分位数可以过滤掉极端情况
- 反映真实用户体验:关注大多数用户的性能表现
- 便于设置告警阈值:可以基于分位数设置合理的告警
示例:
CPU 使用率分布:
P50: 1%
P95: 5%
P99: 50%
P99.9: 99%
Max: 99%
通过分位数可以看出:
– 大多数用户(P95)CPU 使用率正常
– 但仍有少量用户(P99)出现性能问题
– 极少数用户(P99.9)出现严重问题
5.3 统计方法选择建议
关键点:平均统计法,易受 bad case 的影响
建议:
- 对于性能监控,应该关注 P95、P99 等分位数指标
- 了解大多数用户的性能表现
-
及时发现性能问题
-
避免单一平均值掩盖问题
- 平均值可能看起来正常,但实际存在性能问题
-
结合分位数和分布图分析
-
结合分布图分析性能数据
- 直方图:了解数据分布情况
- 箱线图:查看数据的分散程度和异常值
-
时间序列图:观察性能趋势
-
设置合理的告警阈值
- 基于 P95 或 P99 设置告警
-
避免基于平均值设置告警(容易被极端值触发)
-
关注异常值
- 虽然分位数可以过滤异常值,但仍需要关注
- 分析异常值的原因,可能是 bug 或性能问题
统计方法对比:
| 统计方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 平均值 | 简单直观 | 易受极端值影响 | 数据分布均匀时 |
| 加权平均 | 可设置权重 | 权重设置困难 | 有明确权重需求时 |
| 中位数(P50) | 不受极端值影响 | 可能忽略问题 | 了解典型情况 |
| P95/P99 | 反映真实体验 | 计算稍复杂 | 性能监控推荐 |
| 最大值 | 发现最坏情况 | 可能过于敏感 | 关注极端情况 |
总结
解析 Instruments Trace 文件是 iOS 性能分析的重要技能。通过理解 Trace 文件的结构、掌握符号化方法、正确解析数据并使用合适的统计方法,可以深入分析应用性能,定位性能瓶颈。
关键要点:
- 理解基本概念:
all_weight和self_weight的区别和用途 - 掌握符号化流程:从 UUID 获取到地址转换的完整流程
- 了解文件格式:Trace 文件和 XML 格式的结构
- 正确解析数据:按照步骤解析并构建调用树
- 选择合适的统计方法:使用分位数而非平均值进行性能监控
