1、缓存基础概念:什么是图像缓存,为什么需要缓存,缓存的核心指标
各位同学,咱们今天聊点实在的。图像缓存,说白了就是一块专门用来暂存图像数据的内存区域。你想想看,LVGL 在屏幕上画一张图,总不能每次都从 Flash 或者 SD 卡里现读吧?那速度慢得你根本没法忍。
我刚开始用 LVGL 做项目时,就犯过这个错。一个 320x240 的 RGB565 图片,没开缓存,结果界面切换卡得像幻灯片。后来加了缓存,丝般顺滑。嗯,这就是缓存的魅力。
1.1 什么是图像缓存
图像缓存,本质上是一块预先分配好的 RAM 区域。LVGL 把解码后的图像数据暂存在这里,下次再显示同一张图时,直接从缓存里拿,不用重新解码。
举个例子:你有一张 PNG 图标,原始大小可能只有 2KB。但解码成 RGB565 后,它占 320×240×2 = 150KB。每次显示都解码一遍?太浪费了。缓存就是用来存这个解码后的“成品”。
核心要点:缓存存的是“解码后”的像素数据,不是原始文件。原始文件在 Flash 里,缓存里的数据是 CPU/GPU 可以直接拿去刷屏的。
1.2 为什么需要缓存
这个问题我问过不少初学者。答案其实就三个字:快、省、稳。
- 快:从 RAM 读数据比从 Flash/外部存储快 10~100 倍。LVGL 的刷新率全靠这个。
- 省:减少重复解码,降低 CPU 占用。尤其对于 JPEG/PNG 这种解码开销大的格式,缓存能省下大量算力。
- 稳:避免频繁访问外部存储导致的延迟抖动。我在一个项目里遇到过,没缓存时,图片切换偶尔会卡半秒,加了缓存后帧率稳如老狗。
我的经验:在 STM32F429 这类不带硬件 JPEG 解码的 MCU 上,一张 800x480 的 JPEG 图片解码需要 200ms 左右。如果每帧都解码,帧率只有 5fps。加上缓存后,直接飙到 30fps。差距就是这么大。
1.3 缓存的核心指标
做嵌入式的人都知道,资源是有限的。缓存不是越大越好,关键看三个指标:命中率、延迟、容量。
| 指标 | 定义 | 对 LVGL 的影响 |
|---|---|---|
| 命中率 | 缓存命中的次数 / 总访问次数 | 命中率越高,平均加载时间越短 |
| 延迟 | 从发起请求到获取数据的时间 | 影响界面响应速度,尤其是动画场景 |
| 容量 | 缓存能存储的最大数据量 | 决定能缓存多少张图片,影响命中率 |
命中率
命中率是缓存最核心的指标。说白了,就是“我要的图在不在缓存里”。如果命中率 90%,那平均加载时间就是:0.9 × 缓存延迟 + 0.1 × 原始加载延迟。
我曾经在一个智能家居面板项目里,把缓存命中率从 60% 优化到 95%,界面切换速度提升了 3 倍。怎么优化的?其实就是把最常用的 5 张背景图固定缓存,不让它们被踢出去。
注意:命中率不是越高越好。为了追求 100% 命中率而分配超大缓存,可能反而因为内存不足导致系统不稳定。我见过有人给 512KB RAM 的芯片分配了 400KB 缓存,结果其他任务 OOM 了。
延迟
延迟分两种:缓存命中延迟和缓存未命中延迟。
- 命中延迟:从缓存读数据的时间。通常几个 CPU 周期,可以忽略不计。
- 未命中延迟:从原始存储读取 + 解码 + 写入缓存的时间。这才是大头。
你想想看,如果一张图未命中,LVGL 要先去 Flash 读原始数据,然后解码,再写入缓存,最后才能显示。这一套下来,几十到几百毫秒就没了。所以降低未命中延迟的关键,就是提高命中率。
容量
容量是个双刃剑。给多了,内存不够;给少了,命中率低。LVGL 的缓存容量通常由 LV_IMG_CACHE_DEF_SIZE 宏控制,单位是字节。
// 在 lv_conf.h 中配置
#define LV_IMG_CACHE_DEF_SIZE 0 // 0 表示使用默认值(通常为 0,即不限制)
// 实际项目中建议手动设置,例如:
#define LV_IMG_CACHE_DEF_SIZE (32 * 1024) // 32KB 缓存
我个人习惯这样估算缓存容量:先统计项目中所有图片的大小,然后取最常用的 3~5 张图的总大小,再加 20% 余量。比如最常用的图加起来 80KB,那就设 100KB 左右。
1.4 缓存的工作流程
说了这么多理论,咱们看看 LVGL 里缓存到底怎么工作的。下面这张图能帮你理清思路。
从这张图你能看到,LVGL 的缓存逻辑其实很清晰:每次显示图片前,先查缓存。有就直接用,没有就加载解码,然后存一份到缓存里。下次再用就快了。
1.5 缓存的代价
缓存不是免费的午餐。它也有代价,主要体现在三个方面:
- 内存占用:缓存占用的 RAM 不能被其他模块使用。在资源紧张的 MCU 上,这是个需要权衡的问题。
- 管理开销:缓存需要维护索引、LRU 链表等数据结构,会消耗少量 CPU 时间。
- 一致性维护:如果图片文件在外部存储中被修改了,缓存里的旧数据就成了“脏数据”。需要手动刷新缓存。
避坑指南:我曾经在一个 OTA 升级项目里踩过坑。升级后图片文件变了,但缓存没清,结果界面上显示的还是旧图。后来在升级流程里加了 lv_img_cache_invalidate_src(NULL) 清空所有缓存,问题解决。
1.6 什么时候该用缓存
不是所有场景都需要缓存。我总结了几条经验:
| 场景 | 建议 | 原因 |
|---|---|---|
| 频繁切换的图标/背景 | 必须缓存 | 减少重复解码,提升流畅度 |
| 只显示一次的启动图 | 不需要缓存 | 浪费内存,且没有复用机会 |
| 大尺寸 JPEG 图片 | 建议缓存 | 解码开销大,缓存收益明显 |
| 小尺寸 Alpha 图标 | 视情况而定 | 解码快,缓存收益有限 |