Skip to content

perf: optimize APK query pipeline - #5

Merged
MG1937 merged 1 commit into
MG1937:devfrom
TsingShui:perf/apk-query-pipeline
Oct 20, 2026
Merged

MG1937 merged 1 commit into
MG1937:devfrom
TsingShui:perf/apk-query-pipeline

Conversation

@TsingShui

Copy link
Copy Markdown
Contributor

概述

优化大型 APK 的 listclass、listmethod、findrefs 查询管线,保持 CLI 输出和 list_classes -> Result<Vec<String>, String> 的库接口兼容性。

本 PR 只有 1 个 commit,仅包含源码与回归测试;不提交 APK、性能报告、统计 JSON 或基准脚本。

主要实现

  • 按需前缀解压:类查询只读取所需的 DEX id 表与描述符;在请求完整 id 表前探测晚字符串区,必要时提前选择完整解压,避免重复解码。
  • 缓冲区与调度:按 worker 复用解压缓冲区/解码器;按 DEX 顺序分块输出,限制待输出结果的保留窗口,减少大字符串拼接和复制。
  • 索引与引用扫描:使用紧凑索引,单次成员查询直接筛选 id 表行、按需构建类型索引;引用候选扫描先判断索引位图,再校验 opcode 和指令位置。
  • 兼容与插桩:保留逐类字符串行 API;CLI 仍使用分块字节路径,收集方转移缓冲区所有权;细粒度阶段计时按需启用。

性能收益:相对 dev

基线为 24d3770;当前目标分支 dev 的 7b72583 仅增加合并提交,代码树与该基线一致。

整体结果

指标 dev 优化版 降低
11 个用例的耗时中位数之和 1099.30 ms 804.96 ms 26.77%
CPU user 时间中位数之和 8868.58 ms 6417.05 ms 27.64%
CPU sys 时间中位数之和 610.53 ms 464.98 ms 23.84%
各用例峰值物理占用中位数的均值 291.10 MiB 197.63 MiB 32.11%
全部样本中观测到的最大物理占用 478.50 MiB 420.19 MiB 12.19%
全部样本中系统报告的最大 RSS 626.00 MiB 567.72 MiB 9.31%

按各用例的百分比降幅等权平均:耗时降低 23.65%,峰值物理占用降低 29.89%。这与“总耗时之比”和“平均峰值之比”的口径不同。

分用例

耗时为跨轮中位数,物理占用为每次进程采样峰值的跨轮中位数。

用例 dev → 优化版耗时(ms) 耗时降低 物理占用 dev → 优化版(MiB)
listclass / 抖音 93.18 → 34.15 63.3% 281.2 → 66.6
listclass / QQ 84.29 → 35.94 57.4% 259.9 → 91.7
listclass / 淘宝 31.21 → 29.12 6.7% 121.6 → 119.7
listmethod / 抖音 112.92 → 110.62 2.0% 406.3 → 321.7
listmethod / QQ 106.09 → 105.66 0.4% 461.8 → 386.1
findrefs string / 抖音 92.47 → 92.18 0.3% 219.1 → 194.3
findrefs string / QQ 85.90 → 85.63 0.3% 237.6 → 216.7
findrefs method / 抖音 172.84 → 101.05 41.5% 345.7 → 199.6
findrefs method / QQ 152.39 → 89.76 41.1% 373.3 → 210.7
findrefs field / QQ 127.08 → 85.93 32.4% 322.8 → 205.4
findrefs type / 淘宝 40.91 → 34.94 14.6% 173.0 → 161.4

主要时间收益来自类列表和成员引用查询;listmethod 更主要的收益在内存。字符串引用查询和百分之零点几的耗时变化应视为基本持平,不宣称所有用例都显著加速。

淘宝退化的处理

最初的固定前缀阈值会在解完 id 表后才发现字符串区靠后,随后从头完整解压,导致淘宝 listclass 退化。简单改成剩余成本模型收益有限,未保留。

最终通过 DEX 头部附近的字符串偏移提示提前选择解码路径,同轮 dev/中间版/最终版为 31.21 / 34.50 / 29.12 ms。该偏移仅作为选择解码器的提示,不假定字符串物理顺序,不省略任何类,也不按 APK 名称特判。

测量方法与限制

  • 机器:Apple M3 Pro,6P + 6E / 12 逻辑核,36 GiB RAM,macOS 26.5.1;Rust/Cargo 1.98.0。
  • 相同 release 配置、默认 features、默认 12 workers;独立构建二进制,排除编译/测试耗时。
  • 语料:抖音 40.7.0(343 MiB)、QQ 9.3.75(375 MiB)、淘宝 10.66.40(91 MiB)。
  • 固定上述 11 个重命令用例:string 查询 https://,method 查询 onCreate,field 查询 mHandler,type 查询 Landroid/util/。不将 getmanifest/getclass 计入收益。
  • 每个版本、每个用例预热两次,再测 21 轮;随机化用例顺序,轮换版本运行次序,不并行运行被测进程。
  • 每次查询为新进程,从 APK 实时计算完整输出,stdout 指向 /dev/null;无输入预处理、持久化结果缓存或样本特判。
  • 总耗时降低的配对 bootstrap 95% 区间约 26.22%~27.31%;只反映这些样本的随机不确定性,不是跨设备/任意 APK 的保证。
  • 物理占用用 macOS ri_phys_footprint 每 1 ms 采样,包含分配器保留等系统计费,不等同于活跃 Rust 堆或 RSS,极短峰值可能遗漏;RSS 另外通过 wait4/ru_maxrss 记录。
  • 上表来自最终算法版本的固定测量点。随后恢复库 API 的补丁另外做了 21 轮配对验证:CLI 总耗时 805.53 → 803.42 ms,变化区间包含零,视为持平,不额外计入收益。最终 squash 只移除报告/实验产物并恢复原有换行风格。

验证

  • cargo test --release --locked:79 个测试通过。
  • cargo test --release --locked --no-default-features --test listclass_api:4 个新增 API 测试通过。
  • 13 个真实 APK CLI 回归输出的 SHA-256 与字节数一致,包含两个不计性能的 getclass 回归。
  • 额外 21 种单线程精确/模糊/不存在查询,三个 APK 上与基线逐字节一致。
  • 新增字段/方法匹配模式、Unicode、物理顺序、前缀布局和逐类字符串 API 测试;保留多 DEX、DEX 041 与 MUTF-8 既有测试。
  • 清理整文件换行符转换噪声;未改动 .gitignore、Manifest 或提交报告文件。

建议 review 重点

前缀解析的异常数据/DEX 041 边界、缓冲区所有权、并发错误退出,以及 libdeflate 路径沿用的 unsafe 输出缓冲初始化约束。现有解压路径仍有 set_len 后向解码器传递切片的安全债务,本次没有解决,合并前需要重点审阅;样本输出一致和测试通过不构成对任意畸形输入的安全证明。

- Decode only class-query prefixes and detect late string pools before inflating full id tables.
- Reuse worker inflate buffers/decoders and stream listings in deterministic order with bounded result retention.
- Use compact locator maps, direct one-shot member queries and index-first reference candidate scans.
- Make phase profiling opt-in; preserve CLI output and the list_classes string-row API.
- Add regression coverage for query match modes, prefix layouts, record boundaries and DEX 041.

Validation: cargo test --release --locked (79 tests), plus byte-identical outputs for 13 real-APK regression cases.
@MG1937

MG1937 commented Oct 20, 2026

Copy link
Copy Markdown
Owner

概述

优化大型 APK 的 listclass、listmethod、findrefs 查询管线,保持 CLI 输出和 list_classes -> Result<Vec<String>, String> 的库接口兼容性。

本 PR 只有 1 个 commit,仅包含源码与回归测试;不提交 APK、性能报告、统计 JSON 或基准脚本。

主要实现

  • 按需前缀解压:类查询只读取所需的 DEX id 表与描述符;在请求完整 id 表前探测晚字符串区,必要时提前选择完整解压,避免重复解码。
  • 缓冲区与调度:按 worker 复用解压缓冲区/解码器;按 DEX 顺序分块输出,限制待输出结果的保留窗口,减少大字符串拼接和复制。
  • 索引与引用扫描:使用紧凑索引,单次成员查询直接筛选 id 表行、按需构建类型索引;引用候选扫描先判断索引位图,再校验 opcode 和指令位置。
  • 兼容与插桩:保留逐类字符串行 API;CLI 仍使用分块字节路径,收集方转移缓冲区所有权;细粒度阶段计时按需启用。

性能收益:相对 dev

基线为 24d3770;当前目标分支 dev 的 7b72583 仅增加合并提交,代码树与该基线一致。

整体结果

指标 dev 优化版 降低
11 个用例的耗时中位数之和 1099.30 ms 804.96 ms 26.77%
CPU user 时间中位数之和 8868.58 ms 6417.05 ms 27.64%
CPU sys 时间中位数之和 610.53 ms 464.98 ms 23.84%
各用例峰值物理占用中位数的均值 291.10 MiB 197.63 MiB 32.11%
全部样本中观测到的最大物理占用 478.50 MiB 420.19 MiB 12.19%
全部样本中系统报告的最大 RSS 626.00 MiB 567.72 MiB 9.31%
按各用例的百分比降幅等权平均:耗时降低 23.65%,峰值物理占用降低 29.89%。这与“总耗时之比”和“平均峰值之比”的口径不同。

分用例

耗时为跨轮中位数,物理占用为每次进程采样峰值的跨轮中位数。

用例 dev → 优化版耗时(ms) 耗时降低 物理占用 dev → 优化版(MiB)
listclass / 抖音 93.18 → 34.15 63.3% 281.2 → 66.6
listclass / QQ 84.29 → 35.94 57.4% 259.9 → 91.7
listclass / 淘宝 31.21 → 29.12 6.7% 121.6 → 119.7
listmethod / 抖音 112.92 → 110.62 2.0% 406.3 → 321.7
listmethod / QQ 106.09 → 105.66 0.4% 461.8 → 386.1
findrefs string / 抖音 92.47 → 92.18 0.3% 219.1 → 194.3
findrefs string / QQ 85.90 → 85.63 0.3% 237.6 → 216.7
findrefs method / 抖音 172.84 → 101.05 41.5% 345.7 → 199.6
findrefs method / QQ 152.39 → 89.76 41.1% 373.3 → 210.7
findrefs field / QQ 127.08 → 85.93 32.4% 322.8 → 205.4
findrefs type / 淘宝 40.91 → 34.94 14.6% 173.0 → 161.4
主要时间收益来自类列表和成员引用查询;listmethod 更主要的收益在内存。字符串引用查询和百分之零点几的耗时变化应视为基本持平,不宣称所有用例都显著加速。

淘宝退化的处理

最初的固定前缀阈值会在解完 id 表后才发现字符串区靠后,随后从头完整解压,导致淘宝 listclass 退化。简单改成剩余成本模型收益有限,未保留。

最终通过 DEX 头部附近的字符串偏移提示提前选择解码路径,同轮 dev/中间版/最终版为 31.21 / 34.50 / 29.12 ms。该偏移仅作为选择解码器的提示,不假定字符串物理顺序,不省略任何类,也不按 APK 名称特判。

测量方法与限制

  • 机器:Apple M3 Pro,6P + 6E / 12 逻辑核,36 GiB RAM,macOS 26.5.1;Rust/Cargo 1.98.0。
  • 相同 release 配置、默认 features、默认 12 workers;独立构建二进制,排除编译/测试耗时。
  • 语料:抖音 40.7.0(343 MiB)、QQ 9.3.75(375 MiB)、淘宝 10.66.40(91 MiB)。
  • 固定上述 11 个重命令用例:string 查询 https://,method 查询 onCreate,field 查询 mHandler,type 查询 Landroid/util/。不将 getmanifest/getclass 计入收益。
  • 每个版本、每个用例预热两次,再测 21 轮;随机化用例顺序,轮换版本运行次序,不并行运行被测进程。
  • 每次查询为新进程,从 APK 实时计算完整输出,stdout 指向 /dev/null;无输入预处理、持久化结果缓存或样本特判。
  • 总耗时降低的配对 bootstrap 95% 区间约 26.22%~27.31%;只反映这些样本的随机不确定性,不是跨设备/任意 APK 的保证。
  • 物理占用用 macOS ri_phys_footprint 每 1 ms 采样,包含分配器保留等系统计费,不等同于活跃 Rust 堆或 RSS,极短峰值可能遗漏;RSS 另外通过 wait4/ru_maxrss 记录。
  • 上表来自最终算法版本的固定测量点。随后恢复库 API 的补丁另外做了 21 轮配对验证:CLI 总耗时 805.53 → 803.42 ms,变化区间包含零,视为持平,不额外计入收益。最终 squash 只移除报告/实验产物并恢复原有换行风格。

验证

  • cargo test --release --locked:79 个测试通过。
  • cargo test --release --locked --no-default-features --test listclass_api:4 个新增 API 测试通过。
  • 13 个真实 APK CLI 回归输出的 SHA-256 与字节数一致,包含两个不计性能的 getclass 回归。
  • 额外 21 种单线程精确/模糊/不存在查询,三个 APK 上与基线逐字节一致。
  • 新增字段/方法匹配模式、Unicode、物理顺序、前缀布局和逐类字符串 API 测试;保留多 DEX、DEX 041 与 MUTF-8 既有测试。
  • 清理整文件换行符转换噪声;未改动 .gitignore、Manifest 或提交报告文件。

建议 review 重点

前缀解析的异常数据/DEX 041 边界、缓冲区所有权、并发错误退出,以及 libdeflate 路径沿用的 unsafe 输出缓冲初始化约束。现有解压路径仍有 set_len 后向解码器传递切片的安全债务,本次没有解决,合并前需要重点审阅;样本输出一致和测试通过不构成对任意畸形输入的安全证明。

确定前缀再按需解压的实现很tricky,我很喜欢👍

@MG1937
MG1937 merged commit c4b9345 into MG1937:dev Oct 20, 2026
2 of 3 checks passed
@TsingShui
TsingShui deleted the perf/apk-query-pipeline branch October 21, 2026 02:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants