ohos-dev-distributed-mmi-ram-rom-measurement について
multimodalinputのコード変更によるROM・RAMの差を測定するスキルです。セクション分析、実機測定、コードに基づく推定を組み合わせ、比較条件やメモリの帰属、安全なデバイス復元も記録します。
主な機能
- 01readelfによるROMセクション比較
- 02smapsとhidumperによるRAM測定
- 03コード差分からヒープやオブジェクトを推定
- 04ライブラリ・プロセス別のメモリ増分特定
- 05測定前の機能経路実行を必須化
- 06破壊的なデバイス操作は承認後に実施
活用シーン
- 01PR前のROM・RAM影響の定量化
- 02コードレビュー用のメモリ測定結果の作成
- 03multimodalinput変更後のメモリ増加の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ openharmonyinsight/ openharmony-skills --skill ohos-dev-distributed-mmi-ram-rom-measurementインストール後、Claude Code で/ohos-dev-distributed-mmi-ram-rom-measurementを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonREADME.mdtest-results.mdreferences/smaps-pss-attribution.mdtheoretical-ram-estimation.md詳細
MMI RAM/ROM 影響度の測定
multimodalinput サブシステムのコード変更前後における、ROM(二進数サイズ)と RAM(実行時メモリ)使用量の変化を測定します。
前提となる skill:コンパイルやデプロイ、デバイス接続などのインフラについては、ohos-dev-distributed-mmi-device-test-harness を参照してください。
使用するタイミング
- PR の提出前に、コード変更が二進数サイズに与える影響を定量化する必要がある場合
- リファクタリングや新機能によってメモリが膨張しないことを証明する必要がある場合
- レビューで RAM/ROM データの提示を求められた場合
安全性と比較可能性の条件
システムの .so ファイルの置き換え、/ の読み書き可能な状態での再マウント、デバイスの再起動は、破壊的なテスト操作です。ユーザーから明示的な承認を得た場合にのみ実施してください。置き換えるすべてのライブラリの .bak コピーを保持し、ワークフローを終了する前にデバイスを指定された最終状態に戻してください。
RAM/ROM の結論を比較可能とするには、次の条件を記録する必要があります。
- ベースラインと現行イメージ/ビルド、または
.soの由来 - 置き換えたライブラリの正確な一覧
- 両方の計測前に同じ機能パスを実行したかどうか
- プロセス単位の数値が、アイドル状態同士、機能実行後同士、または両者の混在のいずれか
ベースラインがアイドル状態で、現行の計測が機能実行後の場合、プロセス単位の PSS 差分を機能の RAM コストとして提示しないでください。参考情報としてのみ使用し、ライブラリの PSS とコードレベルの理論分析を根拠としてください。
3 つの測定柱
この skill では、相互に裏付け合う 3 つの観点から RAM への影響を測定する必要があります。
| 観点 | 方法 | 反映する内容 | 制約 |
|---|---|---|---|
| ROM のセグメント単位 | readelf -S | 静的マッピングメモリ(.text/.rodata/.bss) | ヒープ割り当ては反映しません |
| RAM の実測 | smaps + hidumper による実機での収集 | プロセスの実際の物理メモリ | 収集条件やコンパイルの違いに影響されます |
| RAM の理論値 | git diff による追加コンテナーの分析 + 構造体の推定 | ヒープ割り当てと実行時データ構造 | 人手による推定であり、構造体サイズの分析が正確であることに依存します |
3 つの観点を必ずすべて実施し、相互に照合してください。セグメント単位の推定だけではヒープ割り当てを見落とし、実測だけではノイズの影響を受け、理論分析だけでは実際の挙動を確認できません。
測定対象ライブラリとプロセスの範囲
まずコード変更に基づいて影響範囲を特定してください。サーバー側プロセスだけを確認すればよいと決めつけてはいけません。少なくとも、変更が以下のライブラリに影響するかを確認してください。
| ライブラリ | 役割 | RAM 分析の範囲 |
|---|---|---|
libmmi-server.z.so | サーバーの主要ロジック | multimodalinput プロセス |
libmmi-server-common.z.so | サーバー共通機能 | multimodalinput プロセス |
libmmi-util.z.so | ユーティリティライブラリ | このライブラリを読み込むすべての対象プロセス |
libmmi-client.z.so | クライアント API/プロキシ | 該当 API を呼び出すクライアントプロセス。サーバー側プロセスの PSS で代用することはできません |
ルール:
- ROM 分析では、
libmmi-client.z.soを含む、影響を受けるすべての libmmi.soを対象にしてください。 - RAM 分析では、実際に読み込まれるプロセスに基づいて対象を決めてください。通常、
libmmi-client.z.soはmultimodalinputサーバー側プロセスには読み込まれません。クライアント API、proxy、NAPI、またはテストバイナリのパスが影響を受ける場合は、libmmi-client.z.soを読み込むクライアントプロセスを選び、機能パスを実行して、そのプロセスの smaps/hidumper データを収集してください。 - 変更がサーバー内部のパスだけに影響する場合、client so がサーバー側プロセスに読み込まれていないことを説明できます。ただし、コード diff、シンボル、またはセグメント単位のデータを用いて、
libmmi-client.z.soが本当に変化していないこと、または今回の RAM に関する結論に関係しないことを確認してください。 - 最終レポートには「ライブラリ → プロセス → 機能パスの実行有無 → RAM の結論への採用有無」の対応関係を記載し、サーバー側の PSS を MMI 全体の RAM コストと誤認しないようにしてください。
ROM の測定方法
1. ファイル単位の比較(簡易)
BUILD_OUT="code/out/rk3568/multimodalinput/input"
ls -la $BUILD_OUT/libmmi-*.z.so
2. セグメント単位の比較(高精度、推奨)
readelf -S を使用して主要なセグメントを分析します。
readelf -S libmmi-server.z.so | grep -E '\.text|\.rodata|\.data|\.bss'
| セグメント | 意味 | 影響 |
|---|---|---|
.text | 実行可能コード | ROM に直接影響します。shared clean としてマッピングされ、RAM PSS に影響します |
.rodata | 読み取り専用データ(文字列定数など) | ROM と shared clean PSS に影響します |
.data | 初期化済みグローバル変数 | ROM と private dirty に影響します |
.bss | ゼロ初期化されたグローバル変数 | ROM には影響しません。anonymous RAM に影響します |
.data.rel.ro | 再配置後に読み取り専用となる領域 | ROM に影響します |
3. ベースラインの取得方法
方法 A:デバイス上の .bak ファイル(推奨)
変更版の .so をデプロイする前に、元の .so をバックアップします。
$HDC shell "cp /system/lib/libmmi-server.z.so /data/local/tmp/libmmi-server.z.so.bak"
# 部署新 .so...
# 之后拉取基线
$HDC file recv /data/local/tmp/libmmi-server.z.so.bak ./libmmi-server.z.so.bak
方法 B:CI のベースラインビルド
CI の日次ビルドイメージから、同じバージョンの .so を抽出してベースラインとして使用します。
RAM の測定方法
1. プロセス PID の特定
$HDC shell "pidof multimodalinput"
PID の安定性に注意:multimodalinput はシステムサービスのため、watchdog による再起動で PID が変わることがあります。複数の hdc コマンドを実行する間に PID が変わる可能性があります。解決方法:コマンド置換を使用し、1 回の hdc shell 呼び出しで PID の特定とデータの収集を行います。
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
2. smaps_rollup(プロセス単位の集計)
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
主なフィールド:
- Pss:共有割合に応じて按分した実際の物理メモリ(実際の使用量を最もよく反映します)
- Rss:常駐物理メモリ(共有ページ全体を含みます)
- Size:仮想アドレス空間のサイズ
3. smaps のライブラリ別 PSS(重要)
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps" > smaps.txt
解析スクリプトと注意事項については、references/smaps-pss-attribution.md を参照してください。重要なのは、各 mapping header で現在のライブラリ名をリセットし、anonymous PSS が直前の .so に誤って帰属しないようにすることです。
4. hidumper --mem(システム単位)
$HDC shell "hidumper --mem \$(pidof multimodalinput)"
出力には、native heap、.so mmap PSS、total PSS などの分類が含まれます。
注意:hidumper の出力は非常に長くなることがあり、hdc shell 経由では途中で切り詰められる場合があります。ファイルにリダイレクトしてから取得できます。
$HDC shell "hidumper --mem \$(pidof multimodalinput) > /data/local/tmp/hidumper_mem.txt"
$HDC file recv /data/local/tmp/hidumper_mem.txt ./hidumper_mem.txt
5. RAM 增量归因(必须)
每一个 RAM 增长结论都必须回答“增长来自哪里”。不能只报告 total PSS 或
multimodalinput 进程 PSS 差值。至少把增长拆成以下来源并给出证据:
| 来源 | 证据 | 处理要求 |
|---|---|---|
新增/变化的 .so 映射 | baseline/current smaps 中的 mapped .so 列表和 PSS | 列出每个 .so 的基线 PSS、当前 PSS、差值、是否新增加载 |
| libmmi 代码段变化 | readelf -S + smaps per-library PSS | 单独列出 libmmi 各库 PSS 增量和对应 section 差异 |
| native heap 增长 | hidumper --mem native heap + 代码 diff | 对应到新增容器、对象、缓存、RS 资源或生命周期状态 |
| anonymous/file PSS 增长 | smaps_rollup Pss_Anon/Pss_File 或 smaps 分类 | 说明是否来自堆、匿名映射、文件映射、测试框架或系统状态 |
| 测试环境/框架噪声 | 新增加载的测试二进制依赖、input-test 框架等 .so | 与目标特性分开,不得直接归因给代码变更 |
| 未归因残差 | total delta - 已解释 delta | 单独列出;残差过大时结论只能标记为“不完整归因” |
推荐从 smaps 中按完整路径聚合所有 .so PSS,而不是只匹配 libmmi;参考脚本见 references/smaps-pss-attribution.md。
对 baseline/current 两份结果做 outer join,生成“新增/变化的已加载 .so”表:
| 进程 | .so 路径 | 基线 PSS(kB) | 当前 PSS(kB) | 差值(kB) | 是否新增加载 | 归因 |
|---|---|---|---|---|---|---|
| multimodalinput | /system/lib/libmmi-server.z.so | X | Y | +/-Z | no | libmmi server 代码段变化 |
| multimodalinput | /system/lib/libxxx.z.so | 0 | Y | +Y | yes | 功能路径触发 / 测试框架 / 环境噪声 |
如果内存增长主要来自加载了新的 .so,报告必须列出这些 .so 的名称、路径、
PSS 增量和触发原因;不能只写“.so mmap PSS 增长”。如果无法证明某个新加载库由目标
特性触发,应归入“环境/测试框架差异”或“未归因”,不要归入目标特性成本。
6. 基线 RAM 获取
精确方法:临时恢复 .bak .so → 重启 → 采集 → 恢复修改版 .so
# 1. 备份当前修改版
$HDC shell "mount -o rw,remount /" # RK3568 根文件系统在 /,不是 /system
$HDC shell "cp /system/lib/libmmi-server.z.so /data/local/tmp/libmmi-server.z.so.new"
# 对 libmmi-server-common.z.so, libmmi-util.z.so 同样操作
# 2. 恢复基线
$HDC shell "cp /data/local/tmp/libmmi-server.z.so.bak /system/lib/libmmi-server.z.so"
# 对每个库同样恢复 .bak
$HDC shell "reboot"
# 等待重启 (~60s)...
# 3. 采集基线 RAM
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps_rollup"
$HDC shell "cat /proc/\$(pidof multimodalinput)/smaps" > smaps_baseline.txt
$HDC shell "hidumper --mem \$(pidof multimodalinput) > /data/local/tmp/hidumper_baseline.txt"
# 4. 恢复修改版
$HDC shell "mount -o rw,remount /"
$HDC shell "cp /data/local/tmp/libmmi-server.z.so.new /system/lib/libmmi-server.z.so"
# 对每个库同样恢复 .new
$HDC shell "reboot"
RK3568 mount 注意:根文件系统挂载在
/,不是/system。mount -o rw,remount /system会报 "not in /proc/mounts" 错误。正确命令是mount -o rw,remount /。
估算方法(不需要重启):
基于段级分析推算 RAM 变化:
.text+.rodata差值 → 影响 shared clean PSS.bss差值 → 影响 anonymous 内存.data差值 → 影响 private dirty
当段差值很小(< 1%)且 .bss 无变化时,可以直接用估算方法而无需重启。
7. 采集前必须执行功能路径
关键约束:仅部署 .so 并重启是不够的。如果变更引入了懒分配(lazy allocation)的数据结构,必须在采集前执行完整的功能路径,确保所有运行时状态被实际分配:
部署 .so → 重启 → 等待系统稳定 (30s) → 执行功能路径 → 等待稳定 (5s) → 采集
功能路径按变更内容定义,不要固定成某一个特性用例。通用模板:
- 初始化变更依赖的系统状态(如 display、window、device、session、permission)。
- 调用新增或修改的 public API、IPC、server handler、callback 或事件路径。
- 触发懒分配和缓存路径(如首次事件、重复事件、边界设备、窗口、显示组)。
- 覆盖清理路径(解绑、销毁、断连、窗口删除、服务重启前后状态)。
- 记录实际执行的步骤,并说明哪些受影响库和进程被该路径覆盖。
例如 display group 绑定可以用 UpdateDisplayInfo、UpdateWindowInfo、
BindDeviceToDisplayGroupByDisplay 和事件注入触发;其他特性必须替换成自己的最小完备功能路径。
不执行功能路径的 RAM 采集会低估实际内存占用,因为懒分配的容器仍为空。
代码理论分析方法(第三支柱)
段级估算只能反映静态映射内存,无法反映堆分配(new/malloc)。对于引入新数据结构的特性变更,必须分析代码 diff 中的新增容器和对象。
详细估算规则见 references/theoretical-ram-estimation.md。主流程只要求输出:
- 新增容器、对象、缓存和 RS 资源清单。
- 每类对象的典型场景和上界场景估算。
- 分配时机、清理路径和泄漏风险。
- 理论估算与实测 libmmi PSS/native heap/Pss_Anon 的交叉验证结论。
输出报告模板
## ROM 影响
### 文件级对比
| 库 | 基线(bytes) | 当前(bytes) | 差值 | 比例 |
|----|------------|------------|------|------|
| libmmi-server.z.so | X | Y | +/-Z | +/-N% |
| ... | | | | |
| **合计** | | | | |
### libmmi-server.z.so 段级分析
| 段 | 基线 | 当前 | 差值 |
|----|------|------|------|
| .text | X | Y | +/-Z |
| .rodata | | | |
| .data | | | |
| .bss | | | |
### 影响范围映射
| 库 | 受影响代码路径 | 目标进程 | 是否采集 RAM | 说明 |
|----|--------------|----------|--------------|------|
| libmmi-server.z.so | server handler / event path | multimodalinput | yes/no | |
| libmmi-client.z.so | client API / proxy / NAPI | <client-process-or-test-binary> | yes/no | |
## RAM 影响
### RAM 增长原因归因
#### 进程级增量拆解
| 指标/类别 | 基线(kB) | 当前(kB) | 差值(kB) | 证据来源 | 归因 |
|-----------|----------|----------|----------|----------|------|
| total PSS | X | Y | +/-Z | smaps_rollup / hidumper | 汇总,仅作入口 |
| .so mmap PSS | X | Y | +/-Z | hidumper + smaps `.so` 聚合 | 见“已加载 .so 增量”表 |
| native heap | X | Y | +/-Z | hidumper | 见“heap/anonymous 增量”表 |
| Pss_Anon | X | Y | +/-Z | smaps_rollup | heap/anonymous/运行时状态 |
| Pss_File | X | Y | +/-Z | smaps_rollup | 文件映射或加载库变化 |
#### 已加载 .so 增量
| 进程 | `.so` 路径 | 基线 PSS(kB) | 当前 PSS(kB) | 差值(kB) | 是否新增加载 | 触发路径/归因 |
|------|-----------|--------------|--------------|----------|--------------|---------------|
| multimodalinput | /system/lib/libmmi-server.z.so | X | Y | +/-Z | no | 目标 libmmi 代码变化 |
| <process> | /system/lib/libxxx.z.so | 0 | Y | +Y | yes | 功能路径触发 / 测试框架 / 环境噪声 |
#### libmmi PSS 增量
| 库 | 基线 PSS (kB) | 当前 PSS (kB) | 差值 | section 证据 | 归因 |
|----|-------------|---------------|------|--------------|------|
| libmmi-server.z.so | X | Y | +/-Z | .text/.rodata/.data/.bss 差异 | server 代码路径 |
| libmmi-client.z.so | X | Y | +/-Z | .text/.rodata/.data/.bss 差异 | client API/proxy 路径 |
#### heap/anonymous 增量
| 增长项 | 估算增量 | 实测对应指标 | 代码证据 | 分配/释放路径 | 结论 |
|--------|----------|--------------|----------|---------------|------|
| 新增 map/vector/cache | X KB | native heap / Pss_Anon | 文件:行号或 diff 摘要 | 创建/清理函数 | 目标特性成本 |
| 测试框架对象 | X KB | native heap / Pss_Anon | 测试入口或依赖 | 测试进程生命周期 | 环境差异 |
#### 未归因残差
| total PSS 差值 | 已解释 .so 差值 | 已解释 heap/anon 差值 | 未归因残差 | 处理 |
|----------------|----------------|----------------------|------------|------|
| +X kB | +Y kB | +Z kB | +N kB | N 过大时标记结论不完整,并继续补采 smaps/hidumper/功能路径证据 |
### 实测对比(libmmi 库 PSS)
| 库 | 基线 PSS (kB) | 修改版 PSS (kB) | 差值 | 归因 |
|----|-------------|---------------|------|------|
| libmmi-server.z.so | X | Y | +/-Z | section/code path evidence |
| ... | | | |
### 理论分析
| 场景 | 当前实现 | 后续/完整实现(如适用) |
|------|---------|------------------------|
| 不使用路径 | ~X B | ~X B |
| 典型功能路径 | ~X KB | ~X KB |
| 极端上界 | ~X KB/MB | ~X KB/MB |
### 实测 vs 理论对照
[说明两者是否一致,差异原因]
## 结论
ROM: +/-X bytes (+/-N%)
RAM: 服务端进程 PSS +/-X kB;客户端进程 PSS +/-Y kB(如适用);理论典型场景 ~Z KB
Common Mistakes
| 错误 | 后果 | 修复 |
|---|---|---|
用 ls -l 文件大小评估 RAM | 文件大小包含 ELF 头、符号表等不映射到内存的部分 | 用 readelf 段级分析 |
| 忽略 .bss 段 | .bss 不占 ROM 但影响 anonymous RAM | 单独检查 .bss |
| 拿 RSS 做进程间对比 | RSS 包含共享页全量,被多次计算 | 用 PSS |
| 对比不同版本系统的 RAM 数据 | 系统库版本差异导致噪声 | 确保只替换目标 .so,系统基线一致 |
libmmi-client.z.so 只看服务端进程 | client so 不在服务端进程加载,RAM 结论缺失 | 选择加载 client so 的客户端进程或测试二进制单独采集 |
| 重启后不执行功能路径就采集 | 懒分配的数据结构未实际分配,RAM 被低估 | 采集前必须执行完整功能路径触发所有状态分配 |
| 只做段级估算不做理论分析 | 段级估算不反映堆分配(new/malloc),遗漏运行时数据结构 | 结合 diff 中的新增容器做理论内存估算 |
| 只做理论分析不做实测 | 理论估算可能遗漏/高估,无法确认实际表现 | 三支柱(段级 + 实测 + 理论)交叉验证 |
| 只报 total PSS 或 .so mmap PSS 增长 | 无法判断增长来自哪个库、堆对象还是测试环境 | 必须列出进程级分类、每个已加载 .so 的 PSS 增量、heap/anonymous 增量和未归因残差 |
.so mmap PSS 增长但不列新增 .so | 无法判断是否由目标特性加载库导致 | 对 baseline/current 的完整 .so 列表做 outer join,列出新增加载和 PSS 变化 |
把测试框架额外加载的 .so 归因给目标特性 | 夸大特性 RAM 成本 | 未证明由目标功能触发的 .so 归入环境/测试框架差异或未归因 |
| smaps 解析器不重置 current_lib | anonymous mapping 的 PSS 被错误归入上一个 libmmi 库,严重高估 | 每个 mapping header 行必须重置 current_lib |
| PID 在多条 hdc 命令间变化 | 读取了不同进程的 smaps | 使用 $(pidof multimodalinput) 在单次 shell 调用内完成 |
mount -o rw,remount /system | RK3568 根文件系统在 /,报 "not in /proc/mounts" | 使用 mount -o rw,remount / |
| 忽略 Parcelable/RefBase 继承 | 低估含 RefCounter 堆分配的对象大小 | 分析完整继承链,包含 vtable 和 RefCounter |
| 忽略 RS SurfaceNode/BufferQueue | 遗漏最大的单项内存开销(每 group 50-200 KB) | 分析 RS 资源创建路径,区分 nullptr vs 实际创建 |
| 理论分析只看 sizeof 不看堆分配 | 遗漏 std::string 长字符串、vector 元素、map 节点的堆分配 | 分析每个字段的实际内存布局 |