性能调优指南¶
概述¶
EmbeddedGUI 的性能调优主要围绕三个维度展开:PFB(局部帧缓冲)配置、SPI 传输策略、以及绘图 API 的选择。本文基于 QEMU 实测数据,给出各场景下的优化建议。
PFB 尺寸选择策略¶
PFB 是 EmbeddedGUI 的核心设计:屏幕被划分为多个小块,逐块渲染后传输到显示器。PFB 尺寸直接影响内存占用和渲染效率。
尺寸与性能的关系¶
根据 PFB 矩阵测试数据,不同尺寸的性能表现差异显著:
PFB 配置 |
内存 (RGB565) |
分块数 |
适用场景 |
|---|---|---|---|
30x30 (default) |
1.8 KB |
64 |
RAM < 4KB 的极低资源 MCU |
60x30 (medium) |
3.6 KB |
32 |
通用嵌入式场景,性价比最优 |
120x60 (large) |
14.4 KB |
8 |
RAM 充裕时的高性能选择 |
240x15 (fullwidth) |
7.2 KB |
16 |
行扫描优化,适合文本密集界面 |
240x240 (fullscreen) |
112.5 KB |
1 |
仅限 RAM > 128KB 的高端 MCU |
选择建议¶
对于大多数图元绘制(矩形、圆形、圆弧等),PFB 尺寸对 CPU 计算耗时的影响不大(差异 < 10%)。但以下场景差异明显:
文本渲染:PFB 越大,字体缓存命中率越高。
fullscreen模式下 TEXT 耗时仅 0.6ms,而default模式需要 1.9ms填充操作:更大的 PFB 减少了跨块边界的重复计算。CIRCLE_FILL 从 2.9ms (default) 降到 2.3ms (fullscreen)
渐变和阴影:这类逐像素计算的操作受 PFB 尺寸影响较小
推荐策略:
RAM < 8KB:使用 30x30 默认配置
RAM 8-32KB:使用 60x30,兼顾内存和性能
RAM > 32KB:使用 120x60 或 240x15(全宽条带)
RAM > 128KB:可考虑全屏缓冲,但需评估是否值得
配置方法(在 app_egui_config.h 中):
#define EGUI_CONFIG_PFB_WIDTH 60
#define EGUI_CONFIG_PFB_HEIGHT 30
注意:PFB 宽度和高度建议优先选成屏幕尺寸的整数约数。例如 240x240 屏幕,PFB 宽度可选 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 16, 20, 24, 30, 40, 48, 60, 80, 120, 240;如果不能整除,框架也会处理边界块。
双缓冲与三缓冲¶
SPI 传输与缓冲策略¶
SPI 传输是大多数场景下的帧率瓶颈,详见下文 SPI 速度的决定性影响。双缓冲通过让 CPU 和 SPI/DMA 并行工作来缓解这一问题。
根据 SPI 矩阵测试报告 的实测数据:
策略 |
说明 |
效果 |
|---|---|---|
单缓冲 (buf=1) |
CPU 渲染完等 SPI 传输完再渲染下一块 |
帧时间 = CPU + SPI,最慢 |
双缓冲 (buf=2) |
CPU 渲染块 N+1 的同时 SPI 传输块 N |
CPU 和 SPI 并行,帧时间 = max(CPU, SPI) |
三缓冲 (buf=3) |
额外一个缓冲区吸收 CPU/SPI 速度波动 |
理论上更平滑,实测与双缓冲差异很小 |
实测数据显示,从单缓冲切换到双缓冲后,大部分测试项的帧时间从 40-65ms 降到 38.4-39ms,接近 SPI 传输的理论下限。三缓冲和四缓冲相比双缓冲几乎没有额外收益,但多占用一个 PFB 大小的 RAM。
建议:优先使用双缓冲。三缓冲仅在 CPU 渲染时间波动很大时才有意义。
配置方法:
#define EGUI_CONFIG_PFB_BUFFER_COUNT 2
SPI 速度的决定性影响¶
为什么 SPI 才是真正的瓶颈¶
在实际硬件上,帧刷新时间由 CPU 渲染和 SPI 传输两部分组成。启用双缓冲后,两者可以并行,帧时间取决于较慢的一方。根据 SPI 矩阵测试报告 的实测数据,绝大多数绘制操作的 CPU 耗时远低于 SPI 传输时间,这意味着 SPI 速度才是决定帧率的关键因素。

下表对比了典型图元在纯 CPU 渲染、SPI 16MHz 双缓冲、SPI 64MHz 双缓冲三种条件下的帧时间(单位 ms,PFB 30x30,屏幕 240x240):
测试用例 |
CPU only |
SPI 16MHz buf=2 |
SPI 64MHz buf=2 |
CPU 占比 (16MHz) |
|---|---|---|---|---|
RECTANGLE_FILL |
1.132 |
57.600 |
19.200 |
2.0% |
CIRCLE_FILL |
1.960 |
57.600 |
19.200 |
3.4% |
IMAGE_565 |
0.855 |
57.600 |
19.200 |
1.5% |
TEXT |
2.456 |
58.200 |
19.800 |
4.3% |
ARC_FILL |
5.491 |
59.300 |
20.900 |
9.5% |
CIRCLE |
4.560 |
58.300 |
19.900 |
7.9% |
GRADIENT_RECT |
2.122 |
57.600 |
19.200 |
3.7% |
SHADOW_ROUND |
7.676 |
60.800 |
22.300 |
12.6% |
GRADIENT_CIRCLE |
16.355 |
64.700 |
24.300 |
25.3% |
TEXT_ROTATE |
45.469 |
74.000 |
48.300 |
61.4% |
GRADIENT_ROUND_RECT_RING |
54.944 |
81.500 |
55.200 |
67.4% |
关键发现:
SPI 16MHz 下,超过 85% 的测试项帧时间被钉死在 57.6ms。CPU 渲染只用了不到 5ms,剩余 52ms 都在等 SPI 传输完成。CPU 大部分时间处于空闲状态
SPI 从 16MHz 提升到 64MHz,帧时间从 57.6ms 降到 19.2ms,提升 3 倍。而同样的 CPU 渲染优化(比如把 CIRCLE_FILL 从 2ms 优化到 1ms)对帧时间几乎没有影响
只有极少数重计算操作(旋转文字、复杂渐变)的 CPU 耗时超过 SPI 传输时间,此时 CPU 才成为瓶颈
不同屏幕尺寸下的 SPI 瓶颈分析¶
SPI 传输时间与屏幕总像素数成正比。下表列出常见屏幕在不同 SPI 速率下的全屏传输时间(RGB565,PFB 30x30 双缓冲,脏区域为全屏):
屏幕分辨率 |
总字节数 |
SPI 16MHz |
SPI 64MHz |
|---|---|---|---|
128x128 |
32 KB |
~18 ms |
~6 ms |
240x240 |
112.5 KB |
57.6 ms (实测) |
19.2 ms (实测) |
320x480 |
300 KB |
~153 ms |
~51 ms |
对比 CPU 渲染耗时(大多数常规图元 < 10ms),可以看出:
128x128 小屏:SPI 64MHz 以上时传输约 6ms,CPU 和 SPI 耗时接近,两者都需要优化
240x240 中屏:SPI 16MHz 下帧时间被钉死在 57.6ms(约 17 FPS),即使 64MHz 也需要 19.2ms,远超大多数 CPU 渲染时间。SPI 是绝对瓶颈
320x480 大屏:SPI 16MHz 下全屏刷新约 153ms(仅 6.5 FPS),大屏必须依赖脏矩形减少传输量,或使用并行接口(8080/RGB)
实际优化建议¶
既然 SPI 是瓶颈,优化策略的优先级应该调整:
提高 SPI 时钟频率:这是最直接有效的手段。从 16MHz 提升到 64MHz,帧率直接提升 3 倍。大多数 SPI LCD 控制器支持 40-80MHz
启用双缓冲:让 CPU 渲染和 SPI 传输并行。实测数据显示,双缓冲后帧时间从 CPU+SPI 降到 max(CPU, SPI),对于 SPI 瓶颈场景收益巨大
减少脏区域面积:SPI 只传输脏区域覆盖的 PFB 块。如果只有 1/4 屏幕需要更新,SPI 传输时间也降为 1/4
CPU 优化仅在特定场景有意义:只有当 CPU 渲染时间超过 SPI 传输时间时(如旋转文字、复杂渐变),CPU 优化才能降低帧时间。对于常规 UI(按钮、文本、图片),优化 CPU 渲染几乎不影响帧率
经验法则:如果你的 UI 不包含旋转文字或复杂渐变,帧率几乎完全由
屏幕像素数 / SPI 带宽决定。先算这个数字,再决定是否需要优化 CPU 渲染。
详细的 SPI 矩阵测试数据见 SPI 矩阵测试报告。
脏矩形优化¶
EmbeddedGUI 使用脏矩形(dirty rectangle)机制:只有被标记为”脏”的区域才会重新渲染。
如果后续需要继续压缩控件内部的局部刷新范围,可参考脏矩形细粒度优化指南;其中包含自定义子区域、PFB 早裁剪和统计验证的完整做法。
减少 invalidate 范围¶
每次调用 egui_view_invalidate() 会将整个 view 的区域标记为脏。优化要点:
精确 invalidate:只对真正变化的 view 调用 invalidate,避免对父容器调用
避免全屏刷新:不要在定时器回调中对根 view 调用 invalidate
动画优化:动画框架会自动 invalidate 目标 view,不需要手动调用
// 不推荐:刷新整个页面
egui_view_invalidate(EGUI_VIEW_OF(&root_layout));
// 推荐:只刷新变化的控件
egui_view_invalidate(EGUI_VIEW_OF(&label_value));
布局技巧¶
将频繁更新的控件和静态控件分开放置,避免脏矩形覆盖不必要的区域:
把动态数值 label 和静态标题 label 放在不同的区域
避免大面积半透明叠加,因为底层控件也需要重绘
普通 vs HQ 抗锯齿¶
EmbeddedGUI 提供两套绘图 API:普通版和 HQ(高质量抗锯齿)版。
性能对比¶
图元 |
普通 (ms) |
HQ (ms) |
倍率 |
|---|---|---|---|
CIRCLE |
6.19 |
9.23 |
1.5x |
CIRCLE_FILL |
4.48 |
4.40 |
1.0x |
ARC |
2.69 |
6.83 |
2.5x |
ARC_FILL |
6.92 |
18.18 |
2.6x |
LINE |
8.79 |
11.28 |
1.3x |
关键发现:
描边操作(CIRCLE, ARC, LINE)的 HQ 版本开销明显,约 1.3-2.6 倍
填充操作(CIRCLE_FILL)的 HQ 版本几乎无额外开销
ARC_FILL_HQ 开销最大(2.6 倍),因为需要对弧形边缘做亚像素混合
建议:
大尺寸图元(半径 > 30px)使用 HQ 版本,锯齿感明显
小尺寸图元(半径 < 15px)使用普通版本,肉眼难以分辨
填充操作可以放心使用 HQ 版本
动画中的高频重绘优先使用普通版本
图片 vs 矢量绘制¶
性能对比¶
方式 |
耗时 |
内存 |
|---|---|---|
IMAGE_565 (位图) |
9.1ms |
图片大小 (WxHx2 bytes) |
CIRCLE + RECTANGLE (矢量) |
~8.6ms |
几乎为零 |
IMAGE_565_8 (8x 压缩) |
0.19ms |
图片大小 / 8 |
GRADIENT_ROUND_RECT (矢量渐变) |
7.3ms |
几乎为零 |
关键发现:
压缩图片(IMAGE_565_1/2/4/8)的绘制速度极快(< 0.2ms),因为解压后直接 memcpy
未压缩的全屏图片绘制约 9ms,与复杂矢量图形相当
渐变矢量图形(GRADIENT_ROUND_RECT)耗时 7.3ms,比之前的 155ms 大幅优化
建议:
复杂图形(图标、照片)使用压缩位图,速度最快
简单几何形状使用矢量绘制,节省 ROM 空间
渐变背景优先使用矢量渐变 API,避免存储大尺寸渐变位图
文本渲染优化¶
文本渲染的性能与 PFB 尺寸强相关:
PFB 配置 |
TEXT (ms) |
TEXT_RECT (ms) |
|---|---|---|
15x15 |
7.09 |
7.25 |
30x30 |
2.46 |
2.50 |
240x1 |
16.13 |
16.28 |
240x240 |
0.39 |
0.39 |
优化建议:
增大 PFB 尺寸:文本渲染从中受益最大,因为减少了字形的重复解码
使用 TEXT_RECT 裁剪:当文本可能超出显示区域时,TEXT_RECT 通过裁剪避免无效像素写入
字体大小选择:较小的字体(12-16px)渲染更快,因为每个字形的像素更少
静态文本缓存:对于不变的文本,避免每帧重新设置
egui_view_label_set_text()
总结¶
性能调优的优先级排序:
提高 SPI 时钟频率(对帧率影响最大,大多数场景下是决定性因素)
启用双缓冲(让 CPU 和 SPI 并行,消除串行等待)
精确控制脏矩形范围(减少 SPI 传输量和 CPU 重绘量)
选择合适的 PFB 尺寸(影响 CPU 渲染效率,尤其是文本和填充操作)
根据场景选择普通/HQ 绘图 API
合理使用压缩位图 vs 矢量绘制