Core RAM 使用分布与裁剪指南¶
文档范围¶
本文面向
EmbeddedGUI core/framework层,说明当前 RAM 的主要组成、热点分布和裁剪规则。当前示例数据来自
HelloPerformance + PORT=qemu + CPU_ARCH=cortex-m3,用于帮助定位 RAM 大头,不代表所有应用都完全一致。PFB会单独列出,但它属于用户配置项,不计入框架 RAM 优化成果。业务层自定义 view、应用资源、端口私有缓冲区,以及链接脚本保留的 stack/heap 区域,同样会占用同一片 SRAM;本文重点解释 core 侧的占用方式和裁剪顺序。
RAM 三类¶
建议把 EmbeddedGUI 的 RAM 按下面三类来审视:
类别 |
典型位置 |
典型内容 |
规则 |
|---|---|---|---|
静态 RAM |
|
核心状态机、定时器、少量固定元数据、 |
只保留与尺寸无关、上界稳定、收益明确的小对象 |
跟随尺寸走的 buffer |
运行时 scratch / cache |
跟随字体大小、图片大小、屏幕尺寸、 |
必须走 |
Heap |
单次绘制 / 单帧 / 跨帧缓存 |
图像解码 row cache、文字变换 scratch、外部字体 scratch、图片 resize scratch |
优先短生命周期;如需常驻,必须记录 owner、lifetime、bytes 和收益 |
额外需要单独评估的一类是 stack:
stack不属于上面三类,但它和.bss/.data/heap竞争同一片 SRAM。大局部数组同样可能把系统栈顶爆。
任何新引入的大栈对象,都必须说明必要性,并用
-fstack-usage复核。
额外的 HelloPerformance 硬约束:
不接受整个图像尺寸的 resident heap,即使它能换来明显性能收益。
尺寸相关 heap 的上限只接受两类:
不超过
1 * PFB字节;或最多
2行 /2列图像、屏幕尺寸相关 scratch/cache。
如果后续确实需要更高性能版本,也必须在这个上限内做“双版本”选择,不能靠 whole-image cache。
当前 HelloPerformance / QEMU 快照¶
当前 HelloPerformance/QEMU 的 RAM 观测值如下:
项目 |
当前值 |
说明 |
|---|---|---|
|
|
代码和只读数据 |
|
|
已初始化静态 RAM |
|
|
|
|
|
实际未初始化静态符号区 |
|
|
当前链接脚本保留区,不是 core 固定对象本身 |
固定静态 RAM 小计 |
|
|
Heap 峰值 |
|
|
Heap 空闲 current |
|
当前默认低 RAM 方案下,尺寸相关 scratch 最终都会释放 |
alloc/free |
|
完整录制流程结束后配平 |
编译期最大栈帧 |
|
|
当前活跃路径最大栈帧 |
|
|
说明:
llvm-size的bss=2356B里包含了._user_heap_stack=436B,看总表时不要把它全部当成 core 固定静态 RAM。从 core 固定对象角度看,当前真正长期常驻的
.data + .bss只有1972B。其中
egui_pfb=1536B是当前应用配置下的PFB,这是用户自己选择的空间换时间项,不应被当成框架裁剪成果。heap peak=5008B是运行时峰值,不是 idle 常驻占用;当前默认示例结束后current heap会回到0B。如需继续追峰值归因,可在测量构建里额外打开
QEMU_HEAP_TRACE_ACTIONS=1,让 QEMU 按录制 action 输出HEAP_ACTION:<idx>:current/peak/allocs/frees,默认关闭时不会改变当前 RAM 口径。HelloPerformance现在把 QEMU 链接脚本保留栈压到432B,所以静态 RAM headline 已反映这一调整。2026-03-30 又做过一次继续下压到
0x0180(384B)的 A/B,但该 probe 已明确拒绝且没有保留。原因不是egui_canvas_draw_thick_line_scan()不能再降,而是 fresh clean-fstack-usage/main.map/llvm-nm交叉复核后,新的 linked ceiling 会立刻转移到egui_canvas_draw_circle_fill_gradient (424B),后面还有egui_canvas_draw_rectangle_fill_gradient (416B)、egui_canvas_draw_polygon_fill_gradient (408B)、egui_canvas_draw_polygon_fill (408B)和egui_image_rle_draw_image (392B);在“几个字节的不管”这条规则下,这意味着当前默认 reserve 没必要继续为了<=24B的静态 RAM 去冒险。当前正常 QEMU 构建口径是
text=2135636、data=52、bss=2356、static RAM=2408;buffered rotated-text 路径已于2026-03-31删除,相关 alpha8 visible-cache / packed4 fallback heap 口径不再属于当前 shipped path;当前 whole-run heap headline 仍由 codec 场景决定。Core 仍保留默认关闭的 global logical PFB probe 作为测量工具,但
HelloPerformance现在已经接受一个 shipped 的按场景 width hint:egui_core_get_logical_pfb_target_width_hint()对当前高 heap 的 codec hotspot 返回96,其他场景返回0。因此非热点场景仍走原始48x16,被选中的 codec 场景改为逻辑96x8,而物理PFB字节数完全不变。QOI/RLE 解码状态已经从固定
.bss挪到按帧 heap,而当前 shipped 的按场景 logical96x8walk 继续把 whole-run heap headline 压到5008B。当前第一峰值 owner 是IMAGE_TILED_QOI_565_8,已验证的热点梯队是IMAGE_TILED_QOI_565_8 5008B、IMAGE_TILED_RLE_565_8 4816B、IMAGE_RLE_565_8/EXTERN_IMAGE_RLE_565_8 3760B,以及IMAGE_QOI_565_8/EXTERN_IMAGE_QOI_565_8/MASK_IMAGE_QOI_8_ROUND_RECT/EXTERN_MASK_IMAGE_QOI_8_ROUND_RECT 3664B。EGUI_CONFIG_CORE_LOGICAL_PFB_PROBE_ENABLE、EGUI_CONFIG_CORE_LOGICAL_PFB_PROBE_TARGET_WIDTH和弱符号egui_core_get_logical_pfb_target_width_hint()仍主要用于手工 A/B;core 默认返回0,而当前 shipped path 只是在HelloPerformance里额外覆写了一个按场景 hint:当前高 heap codec hotspot 返回96,其他场景仍返回0。IMAGE_TILED_RLE_565_0由于会带来+13.10%回归,明确不进入这组 hint。2026-03-30 的 probe follow-up 还顺手清掉了 resize
src_x_mapheap 路径里遗留的count <= EGUI_CONFIG_PFB_WIDTH断言。这个断言会把逻辑 tile 宽于物理48px的 probe build 直接卡死,但对默认 shipped path 的48x16几何、RAM headline 和性能结果都没有任何变化。历史上的
EGUI_CONFIG_IMAGE_STD_ROUND_RECT_FAST_ROW_CACHE_ENABLE、EGUI_CONFIG_MASK_CIRCLE_FRAME_ROW_CACHE_ENABLE已收回实现私有并保持默认关闭;这两个PFB_HEIGHT相关行缓存如果以后重新评估,也仍然必须走heap,不能回退为静态 RAM 或大栈数组。当前默认 external raw-image shared row cache 也已收紧到
2行上限:EGUI_CONFIG_IMAGE_EXTERNAL_DATA_CACHE_MAX_BYTES=960、EGUI_CONFIG_IMAGE_EXTERNAL_ALPHA_CACHE_MAX_BYTES=480。这不会改变压缩图主导的 whole-run headline,只是当前 headline 现在是5008B;对应240px外部 RGB565+alpha 场景的 scene-local heap 从旧的2880B压到1440B,resize 场景则是1536B。
静态 RAM 分布¶
下面是当前示例里最主要的固定静态符号:
符号 |
字节 |
分类 |
说明 |
|---|---|---|---|
|
|
用户配置项 |
当前 |
|
|
core 元数据 |
核心运行时状态 |
|
|
core 元数据 |
旋转文字小型 affine prepare cache,不是尺寸相关 scratch |
|
|
app 示例对象 |
HelloPerformance 场景对象 |
|
|
core 元数据 |
canvas 运行时状态 |
|
|
app 示例对象 |
app 定时器 |
|
|
port 元数据 |
QEMU 显示驱动状态 |
|
|
core 元数据 |
字体 code lookup 缓存元数据 |
|
|
core 元数据 |
解码 cache 元数据,记录 tail-row cache 窗口信息 |
|
|
heap handle 元数据 |
只保存 heap 缓冲句柄/容量 |
|
|
core 元数据 |
QOI 解码器按帧 heap 状态句柄 |
|
|
core 元数据 |
RLE 解码器按帧 heap 状态句柄 |
|
|
port 元数据 |
QEMU 资源句柄 |
判断原则:
可以留在静态区的,应该是与字体大小、图片大小、屏幕尺寸、
PFB尺寸无关的小型状态对象。如果静态对象只是某个 heap buffer 的
owner / pointer / capacity元数据,通常可以接受,因为真正的大块数据已经不再常驻.bss。PFB必须单独看。它通常是静态 RAM 里最大的一项,但这是应用配置,不是 core 算法残留。QOI/RLE 解码器也改到了这一条线上:当前静态区只保留
qoi_state_ptr/rle_state_ptr两个4B句柄,真正的208B/16B解码状态只在当前帧解码期间上 heap,并在egui_image_std_release_frame_cache()释放。最新 external text/image transform 优化仍遵守这条线:只新增了流式读取和 chunk 对齐控制代码,没有把任何 glyph 或 image 尺寸相关 scratch 落回静态区。
跟随尺寸走的 buffer¶
以下这类 buffer 不允许为了“heap 看起来更小”而回退成静态数组或大栈数组:
跟随字体大小变化
跟随图片大小变化
跟随屏幕尺寸变化
跟随
PFB尺寸变化
当前 core 中典型的尺寸相关 buffer 如下:
模块 |
owner |
尺寸驱动因素 |
当前放置 |
生命周期 |
|---|---|---|---|---|
压缩图单行解码 scratch |
|
图片宽度、像素格式、alpha 行宽 |
|
单次解码 / 当前帧 |
压缩图 row-band / tail-row cache |
|
|
|
当前 refresh walk |
masked QOI visible-span scratch |
|
当前可见列数 |
|
单次 masked alpha 行 / 当前绘制 |
旋转文字 visible alpha8 tile |
|
可见 glyph 变换后包围盒 |
|
当前 refresh walk |
rotated text layout scratch |
|
glyph 数、line 数、布局结果 |
|
单次绘制 |
rotated text tile scratch |
|
可见 glyph 数、line 数 |
|
单次绘制 |
图片 resize |
|
当前绘制宽度 |
|
单次绘制 |
round-rect 图片快速路径 row cache |
|
|
默认关闭;重开时仍走 |
当前 refresh walk(启用时) |
circle mask row cache |
|
|
默认关闭;重开时仍走 |
当前帧(启用时) |
外部字体 glyph / row scratch |
|
字体大小、glyph bitmap 尺寸 |
|
单次绘制 / 当前 refresh walk,visible-tile 外部旋转文字按 |
关于宏的使用边界: |
宏可以表达“是否启用某个能力”或“某个路径的预算上限”。
但不能用宏把尺寸相关 buffer 直接落成固定
.bss、静态局部数组或大栈数组。宏可以决定“需不需要这块 buffer”或“允许用多少 heap”;真正的大块存储仍然必须走
heap。
当前 Heap 分布¶
当前默认示例的 heap 以短生命周期 scratch 为主,峰值 5008B,空闲 current 回到 0B。
默认低 RAM 路径的大头¶
类型 |
典型 owner |
特征 |
备注 |
|---|---|---|---|
压缩图 tail-row cache |
|
当前默认 heap 峰值主因 |
QOI 已去掉首个可见 alpha 段的 heap scratch,RLE alpha 仍保留可见段瞬时 scratch;后续都只保留横向 tail 列 |
rotated text scratch |
|
随文本布局结果变化 |
已改为按 glyph/line 数动态申请 |
图片 resize / round-rect scratch |
|
随绘制宽度、图片高度、 |
resize scratch 默认启用并用完立即释放;round-rect 行缓存默认关闭,若重开仍必须按需从 |
circle mask frame scratch |
|
随 |
默认关闭;若重开,仍必须在当前帧内按需从 |
外部字体 scratch |
|
随 glyph bitmap 尺寸变化 |
用完或 frame 结束释放;visible-tile 外部旋转文字改为 |
默认路径的单场景 heap 热点¶
场景 |
interaction total peak |
说明 |
|---|---|---|
|
|
当前 whole-run 的第一峰值 owner;扩展后的 logical |
|
|
logical |
|
|
logical |
|
|
同上,外部资源版本 |
|
|
logical |
|
|
同上,外部资源版本 |
|
|
QOI alpha8 round-rect 场景,已经明显低于当前 tiled codec 热点 |
|
|
同上,外部资源版本 |
|
|
|
|
|
|
|
|
|
补充说明:
当前 whole-run heap peak 仍然由压缩图场景决定,而不是 text 场景;当前 headline 是
5008B,第一峰值 owner 为IMAGE_TILED_QOI_565_8。所有尺寸相关 scratch 最终都会回到
0Bcurrent heap;默认路径没有常驻 heap 大块缓存。当前压缩图热点可以按 shipped 默认分成三档:tiled codec hotspot
5008B / 4816B,普通 QOI/RLE alpha3760B / 3664B,以及 buffered text3544B / 3334B。这些数值都来自当前 shipped 的按场景 logical96x8路径;它不是被拒绝的“全场景 logical PFB probe”,而是只对当前高 heap codec hotspot 生效的定向 hint,且明确排除了IMAGE_TILED_RLE_565_0。2026-03-29 对
EGUI_CONFIG_IMAGE_CODEC_TAIL_ROW_CACHE_MAX_COLS做过 A/B:144和96都会让 QOI/RLE alpha 场景退化到数倍,说明在当前240px屏宽、PFB_WIDTH=48、横向逐 tile refresh walk 下,192尾列已经是默认单次解码路径的硬下限,而不是随手还能继续压的余量。2026-03-29 第二轮 A/B 又测了
176和184两档 tail cap,并配合EGUI_CONFIG_IMAGE_QOI_CHECKPOINT_COUNT=1/2与EGUI_CONFIG_IMAGE_RLE_CHECKPOINT_ENABLE=1。相对当前d3d37bf基线(IMAGE_QOI_565_8 2.258、EXTERN_IMAGE_QOI_565_8 2.734、IMAGE_RLE_565_8 1.561、EXTERN_IMAGE_RLE_565_8 2.385),四组组合仍然退化+45% ~ +66%,因此不再继续补 heap 数据,直接判定默认路径拒绝。这背后的几何约束已经比较清楚:在
240px图宽、48px水平 PFB walk 下,首个可见 tile 覆盖0..47,后续横向邻居需要的列并集是48..239,正好就是192列。只要单次 tail cache 小于192,最后一个 tile 就必然 miss,进而触发额外 row-band 重解;checkpoint 只能把解码状态恢复到 row-band 起点,并不能改变这个覆盖条件。这里是根据当前 walk 顺序做的推导,上面的实测数据正好印证了这个结论。2026-03-29 还做过
tail alpha sparseA/B:尝试只保留 raw RGB565 tail-row cache,把 alpha8 改成 sparse row table(-DEGUI_CONFIG_IMAGE_CODEC_TAIL_ALPHA_SPARSE_ENABLE=1)。这一轮测量发生在后续qoi_state/rle_state -> frame heap之前,所以它只覆盖 row-cache 本体,不含现在额外的208B/16B解码状态。单场景 heap 当时确实从9360B降到7512B,即-1848B,但IMAGE_QOI_565_8 / EXTERN_IMAGE_QOI_565_8 / IMAGE_RLE_565_8 / EXTERN_IMAGE_RLE_565_8分别退化到8.988 / 9.464 / 8.296 / 9.119 ms,相对当时默认2.264 / 2.740 / 1.572 / 2.395 ms是+245% ~ +428%。同时 alloc/free 也从32 / 32跳到376 / 376,说明 sparse pack/expand 的运行时成本远超当前 RAM 收益,因此这一方向同样明确拒绝,不进入默认实现。
配置 |
|
|
|
|
结论 |
|---|---|---|---|---|---|
基线( |
|
|
|
|
后续 frame-heap 状态调整前的默认基线 |
|
|
|
|
|
拒绝 |
|
|
|
|
|
拒绝 |
|
|
|
|
|
拒绝 |
|
|
|
|
|
拒绝 |
按同样的几何关系推算,
176和184其实也只是在192列 tail 上少了16/8列,checkpoint 开销扣除前的 raw tail 节省不过768B/384B。在已经实测到+45% ~ +66%退化的前提下,这条线没有继续做默认路径 heap 复测的必要。这组 buffered rotated-text heap/perf A/B 已经转入历史记录:当前代码已删除整条路径,后续默认口径不再单独跟踪这组 alpha8 visible-cache / packed4 fallback 指标。
当前默认 external raw-image 路径也已经强制遵守
<=2行 / 列尺寸相关 heap 约束:shared row cache 从旧的1920/960收紧到960/480,因此代表性 scene-local heap 变为EXTERN_IMAGE_565_8 1440B、EXTERN_IMAGE_RESIZE_565_8 1536B、EXTERN_IMAGE_ROTATE_565_8 1440B。2026-03-29 又补做过
1行 external raw-image row cache A/B:把 shared row cache 再压到480/240后,代表性 scene-local heap 会继续降到EXTERN_IMAGE_565_8 720B、EXTERN_IMAGE_RESIZE_565_8 816B、EXTERN_IMAGE_ROTATE_565_8 720B,但 whole-run heap headline 仍然不变,还是被 codec 场景钉在当前的5008B。同一轮实测里,
1行方案在关键 direct-draw / rotate 场景退化超出当前>500B => 10%默认门线:EXTERN_IMAGE_565_1 1.458 -> 1.795 (+23.11%)、EXTERN_IMAGE_565_8 1.630 -> 1.967 (+20.67%)、EXTERN_IMAGE_ROTATE_565_1 13.474 -> 15.161 (+12.52%)、EXTERN_IMAGE_ROTATE_565_8 15.875 -> 19.655 (+23.81%),因此当前正式默认值仍保持2行,1行只保留为测量用 override,不进入默认实现。这一轮属于“按 RAM 规则收口”的默认低 RAM 路径,而不是 perf-neutral 优化:旧的
4行 external raw-image 默认 cache 已经超出当前 HelloPerformance 上限,因此现在接受一部分外部原始图片场景变慢,最差实测为EXTERN_IMAGE_565_1 +13.11%、EXTERN_IMAGE_ROTATE_565_1 +12.56%、EXTERN_IMAGE_ROTATE_TILED_565_8 +12.26%。最新 external transform row-cache 对齐则是在同样
2行 cache 上追回 rotate 性能,不改变1440B/1536Bscene-local heap:EXTERN_IMAGE_ROTATE_565_1 14.696 -> 13.474 (-8.32%)、EXTERN_IMAGE_ROTATE_565_2 15.508 -> 14.106 (-9.04%)、EXTERN_IMAGE_ROTATE_TILED_565_0 11.707 -> 10.630 (-9.20%)、EXTERN_IMAGE_ROTATE_TILED_565_8 17.633 -> 16.029 (-9.10%),而 direct draw / resize 保持0.00%。只要收益不足阈值,就优先保 RAM:
<=500BRAM 变化用5%线,>500BRAM 变化用10%线。如果后续还要继续压缩这一段 heap,手段就不能再是“简单缩 tail 列数”,而需要新的解码/渲染架构,例如改变 PFB walk 顺序,或引入更细粒度的 decoder checkpoint。
Rejected Global Logical PFB Probe A/B¶
2026-03-30 在 core 里补了默认关闭的 logical PFB probe,只用于测量,不改变物理 PFB 字节数。后续 shipped path 接受的并不是“全局 probe 开启”,而是 HelloPerformance 自己提供的按场景 width hint:仅当前高 heap codec hotspot 返回 96,其他场景仍返回 0。下面这些 global / all-scenes probe 仍然全部拒绝。
实验 |
Heap 变化 |
性能结果 |
结论 |
|---|---|---|---|
全 codec 场景逻辑 tile 改为 |
|
QOI alpha 仍有 |
拒绝 |
仅 QOI 场景逻辑 tile 改为 |
|
节省只有 |
拒绝 |
全 codec |
|
RLE checkpoint 对这条实验线几乎没有恢复作用,整体结果与上一行基本相同 |
拒绝 |
MASK_IMAGE_TEST_PERF_ROUND_RECT等局部圆角遮罩场景在更宽逻辑 tile 下确实更快,但 shipped default 必须看整套场景是否满足当前 RAM/perf 规则,不能因为单个热点收益就带进默认路径。python scripts/perf_analysis/main.py的PASSED只表示当前跑通,不代表通过历史结果对比;这三组 probe 实验都是手工对照doc/source/performance/perf_report.md后判定拒绝。2026-03-30 的 follow-up 把 probe-only 的 resize 断言补齐后,
96x8 / 128x6 / 192x4这些更宽 shape 终于可以完整跑完;但它们依然没有一个能通过 shipped default 的全场景门线。
实验 |
Heap 变化 |
性能结果 |
结论 |
|---|---|---|---|
全场景 logical |
未继续重跑 heap |
codec 热点本身大多仍在可接受区间: |
拒绝 |
更宽 logical |
未继续重跑 heap |
更宽 shape 的 targeted spot check 已经跨过门线: |
拒绝 |
最宽 logical |
未继续重跑 heap |
最激进宽度方向更差: |
拒绝 |
这三组更宽 shape 都是在性能已经明显失败后就停止,没有继续补新的 whole-run heap headline,因为 shipped default 的前提是先过完整 RAM/perf 门线,而不是只看 codec 局部热点。
被接受的 shipped 结论比上面的 rejected global probe 更窄:只对当前高 heap codec hotspot 复用 logical
96x8,并且显式排除IMAGE_TILED_RLE_565_0。最新 follow-up 进一步把 whole-run heap 从6448B降到5008B,同时整套 benchmark 仍满足>500B => 10%门线。这个 accepted path 不是上面被拒绝的“全场景96x8”。
不接受的方向¶
下列方案即使性能收益明显,也不再接受:
方向 |
原因 |
|---|---|
whole-image resident cache |
直接按整图尺寸申请 heap,违反 HelloPerformance 的 RAM 上限规则 |
用宏上限把整图或整屏 scratch 固定进 |
本质仍是尺寸相关 buffer,只是把风险从 heap 转移到静态区或栈 |
因此,后续允许保留的高性能变体只能是:
行缓存、列缓存、
PFB级 scratch;最多
2行 /2列图像、屏幕尺寸相关 heap;且必须和默认低 RAM 方案分开记录。
栈使用评估¶
stack 不是本文的主分类,但必须同步审计。当前 -fstack-usage 结果中,较大的栈帧如下:
函数 |
栈帧 |
是否在当前 HelloPerformance 活跃路径 |
说明 |
|---|---|---|---|
|
|
否(仅编译进入,已被当前镜像 GC 丢弃) |
大局部数组热点; |
|
|
否(仅编译进入,已被当前镜像 GC 丢弃) |
大局部 |
|
|
否(仅编译进入,已被当前镜像 GC 丢弃) |
polyline helper 没有链接进当前 HelloPerformance benchmark 镜像 |
|
|
是 |
当前最大的 linked HelloPerformance 栈帧;局部 scratch 为标量状态,不跟随尺寸变化 |
|
|
是 |
当前第二大的 linked HelloPerformance 栈帧;固定 LUT 仍然与尺寸无关 |
|
|
是 |
packed edge/intersection scratch 把原来的 |
|
|
是 |
与渐变 polygon 路径使用同样的 packed scratch 思路 |
|
|
是 |
ring gradient / center dot / value text 拆到小 helper 后,app 侧 draw 帧已不再是热点 |
|
|
是 |
函数级 |
|
|
是 |
外部 whole-image cache 探测已撤掉,函数级 |
|
|
是 |
函数级 |
|
|
是 |
函数级 |
|
|
是 |
函数级 |
|
|
是 |
函数级 |
栈侧规则:
新增局部 buffer 之前,先判断它是否属于尺寸相关 buffer;如果是,直接走
heap。即使不是尺寸相关 buffer,只要局部数组达到几百字节,也应该评估是否会把热路径 stack 顶高。
提交前建议至少对热路径跑一次
-fstack-usage,确认没有新的异常膨胀。Map交叉复核结果:.su里的1200B、1112B、976B这三个大栈帧,在当前 HelloPerformance 镜像里都只出现在Discarded input sections,并未进入最终链接结果。当前最终链接镜像里的最大单函数栈帧是
432B,由egui_canvas_draw_thick_line_scan持有;其后是egui_canvas_draw_circle_fill_gradient (424B)、egui_canvas_draw_rectangle_fill_gradient (416B)、egui_canvas_draw_polygon_fill_gradient (408B)、egui_canvas_draw_polygon_fill (408B)、egui_image_rle_draw_image (392B)、egui_canvas_draw_text_transform (368B)、egui_canvas_draw_image_transform (344B)和egui_canvas_draw_line_hq (288B),最终镜像中仍然没有>=1KB的链接热点。logical PFB 相关路径也已经做过
-fstack-usage复核:core 侧egui_core_get_logical_pfb_target_width_hint=4B、egui_core_get_logical_pfb_probe_width=32B、egui_core_apply_logical_pfb_probe_shape=32B,而HelloPerformance新增的 app 侧 helperegui_view_test_performance_uses_logical_pfb_96_hint=16B、overrideegui_core_get_logical_pfb_target_width_hint=8B,都没有引入新的大栈对象。HelloPerformance 现在使用
__qemu_min_stack_size__=0x01b0,即432BQEMU 预留栈;当前口径已由最新.su/main.map交叉复核、runtime223/223、unit test649/649和python scripts/perf_analysis/main.py --profile cortex-m3 --threshold 10共同支撑。这也说明当前 stack 方向已经接近收尾:后续如果只是把某一个
432B或424B热点单独压下去,而没有一起带动416B/408B/392B这组 active linked frame 下移,就不会带来值得保留的静态 RAM headline 变化。
建议的裁剪顺序¶
当用户继续压缩 RAM 时,建议按下面顺序判断:
先把
PFB当作应用配置项单独评估,不要把减小PFB误记成 core 优化成果。先排查是否还有尺寸相关 buffer 被放在
.bss或stack。再看是否存在“只带来轻微性能收益”的常驻 heap 或静态 cache。
最后再考虑压缩少量固定元数据,因为这类收益通常是几十字节级别。
通常最值得优先关注的是:
图像解码 row cache / single-row scratch
外部图像 row cache
文字变换 scratch
外部字体 glyph scratch
跟随
PFB_HEIGHT的 mask / round-rect row cache跟随绘制宽度变化的 resize map
建议的记录口径¶
后续所有 RAM 相关提交,建议至少同步记录以下数据:
维度 |
建议记录内容 |
|---|---|
静态 RAM |
|
固定静态大头 |
|
Heap |
peak/current、alloc/free、常驻 heap 的 owner/lifetime/bytes |
Stack |
|
结论 |
是否有新的尺寸相关 buffer 落回 |
建议复测命令:
make clean
make all APP=HelloPerformance PORT=qemu CPU_ARCH=cortex-m3
llvm-size output/main.elf
llvm-size -A output/main.elf
llvm-nm -S --size-sort --print-size output/main.elf
Heap 观测:
make clean
make all APP=HelloPerformance PORT=qemu CPU_ARCH=cortex-m3 USER_CFLAGS="-DQEMU_HEAP_MEASURE=1 -DQEMU_HEAP_ACTIONS_APP_RECORDING=1 -DEGUI_CONFIG_FUNCTION_RECORDING_TEST=1"
按 action 追踪 heap 峰值归因:
make clean
make all APP=HelloPerformance PORT=qemu CPU_ARCH=cortex-m3 USER_CFLAGS="-DQEMU_HEAP_MEASURE=1 -DQEMU_HEAP_ACTIONS_APP_RECORDING=1 -DQEMU_HEAP_TRACE_ACTIONS=1 -DEGUI_CONFIG_FUNCTION_RECORDING_TEST=1"
Stack 观测:
make clean
make all APP=HelloPerformance PORT=qemu CPU_ARCH=cortex-m3 USER_CFLAGS="-fstack-usage"
同时建议每次同步更新:
example/HelloPerformance/ram_tracking.md
小结¶
当前 EmbeddedGUI core 的 RAM 使用可以概括为:
固定静态 RAM 已经比较小,当前示例真正常驻的
.data + .bss为1972B,其中PFB就占了1536B。需要跟随字体、图片、屏幕、
PFB尺寸变化的 buffer,必须继续保持heap化,不能为了追求“heap=0”回退到静态区或大栈。当前默认 heap 峰值
5008B主要来自运行时 scratch,空间可回到0B;当前第一峰值 owner 是IMAGE_TILED_QOI_565_8 5008B,随后是IMAGE_TILED_RLE_565_8 4816B、IMAGE_RLE_565_8/EXTERN_IMAGE_RLE_565_8 3760B、IMAGE_QOI_565_8/EXTERN_IMAGE_QOI_565_8/MASK_IMAGE_QOI_8_ROUND_RECT/EXTERN_MASK_IMAGE_QOI_8_ROUND_RECT 3664B;若后续引入常驻 heap,必须明确记录 owner、lifetime、bytes。默认关闭的 core logical PFB probe 仍然保留在树里,作为后续架构 A/B 的测量工具;但
HelloPerformance现在已经接受一个更窄的 shipped 版本,即仅对当前高 heap codec hotspot 启用 logical96x8hint,并明确排除IMAGE_TILED_RLE_565_0。任何更宽或更全局的 shape 仍然必须先通过整套 RAM/perf 门线,不能直接当成正式优化。对于高性能变体,也只能在
1 * PFB或2行 / 列尺寸相关 heap 的约束内做选择,不能回到 whole-image cache。