3. 内存基础概念回顾:SRAM vs DRAM、Cache层级、DMA传输、内存对齐
好,咱们正式开始第三讲。在深入BEV模型部署之前,我觉得有必要先把内存这块儿的基础打牢。你想想看,嵌入式部署说白了就是跟内存打交道——模型参数放哪儿、中间结果存哪儿、怎么搬数据最快。这些搞不清楚,后面优化无从谈起。
我个人习惯,每次接手一个新项目,第一件事就是翻芯片手册里的内存架构图。别嫌麻烦,这步省了,后面调试能让你怀疑人生。
3.1 SRAM vs DRAM:速度与容量的博弈
先说说最基础的。SRAM和DRAM,名字就差一个字母,脾气可差远了。
SRAM(静态随机存取存储器)
- 用触发器存数据,只要不断电,数据就不丢
- 速度快,跟CPU几乎同频
- 但贵啊,一个bit要6个晶体管,面积大得吓人
- 嵌入式芯片里通常只有几百KB到几MB
DRAM(动态随机存取存储器)
- 靠电容存数据,电容会漏电,得定期刷新
- 速度慢一些,但容量大,成本低
- 一个bit只要1个晶体管加1个电容
- 咱们常说的DDR4、LPDDR5,都是DRAM
核心区别一句话:SRAM是跑车,快但装不了多少东西;DRAM是卡车,能装但跑得慢。
我在项目中遇到过这么个事:有个同事把模型的所有权重都放在SRAM里,结果编译直接报内存不足。后来一查,模型30MB,SRAM才2MB。嗯,这就是没搞清楚SRAM和DRAM的区别。
| 特性 | SRAM | DRAM |
|---|---|---|
| 速度 | 几纳秒 | 几十纳秒 |
| 容量 | KB~MB级 | MB~GB级 |
| 成本 | 高 | 低 |
| 功耗 | 较低 | 较高(需刷新) |
| 典型用途 | Cache、片上内存 | 主存、显存 |
实战建议:BEV模型部署时,把最频繁访问的层(比如前几层卷积)的权重放在SRAM里,剩下的放DRAM。这叫「热数据驻留」,能省不少带宽。
3.2 Cache层级:为什么你的程序跑得慢?
好,接下来聊Cache。说白了,Cache就是CPU和主存之间的「缓冲带」。
为什么会需要Cache?因为CPU太快了,DRAM太慢了。CPU执行一条指令可能只要0.3纳秒,但从DRAM读数据要几十纳秒。这差距,好比让博尔特等公交车。
典型的嵌入式芯片Cache分三级:
- L1 Cache:分指令Cache和数据Cache,一般各32KB。速度跟CPU一样快。
- L2 Cache:统一缓存,256KB~2MB。稍微慢点,但容量大。
- L3 Cache:有些芯片有,多核共享,几MB到十几MB。
我记得有一次调一个BEV模型的前向推理,发现某个算子执行时间忽高忽低。查了半天,原来是数据在Cache里「颠簸」——每次访问都miss,然后去DRAM搬数据。后来我把数据排布改了一下,让访问模式更连续,性能直接翻倍。
避坑指南:我曾经以为Cache越大越好,后来发现不是这么回事。Cache太大,命中延迟也会增加。而且多核共享的L3 Cache,一个核把数据挤出去,另一个核就得重新加载。这就是所谓的「Cache污染」。
你想想看,BEV模型里那么多特征图,如果每个特征图的访问都不在Cache里,那性能能好才怪。所以后面我们会专门讲「Cache友好型」的数据排布策略。
3.3 DMA传输:别让CPU干苦力
DMA,全称Direct Memory Access,直接内存访问。这玩意儿,说白了就是让硬件自己搬数据,CPU只管发号施令。
没有DMA的时候,CPU得亲自把数据从A搬到B。这就像老板亲自搬砖,效率极低。有了DMA,CPU说「把这块数据从DRAM搬到SRAM」,然后就可以去干别的活了,DMA控制器自己搞定。
在BEV模型部署中,DMA特别重要。因为模型推理时,数据要在DRAM和SRAM之间来回倒腾。如果没有DMA,CPU得花大量时间在数据搬运上,真正算数的时间反而少了。
关键点:DMA传输可以跟CPU计算「流水线并行」。比如CPU在算第N层的时候,DMA已经在搬第N+1层的数据了。这叫「双缓冲」或「乒乓缓冲」。
我建议你在设计推理框架时,一定要把DMA传输和计算重叠起来。具体怎么做?后面章节会详细讲,这里先记住一个原则:不要让CPU等数据,让数据等CPU。
3.4 内存对齐:看似小事,实则大事
最后聊内存对齐。这玩意儿,新手往往不重视,但老手都知道它的厉害。
什么叫内存对齐?简单说,就是数据在内存里的起始地址,必须是某个值的整数倍。比如4字节对齐,地址就得是4的倍数;8字节对齐,地址就得是8的倍数。
为什么需要对齐?因为CPU读内存不是按字节读的,而是按「字」读的。32位CPU一次读4字节,64位CPU一次读8字节。如果数据没对齐,CPU可能得读两次才能拿到完整数据,性能直接腰斩。
举个例子:
// 没对齐的情况
struct __attribute__((packed)) {
char a; // 1字节
int b; // 4字节,但起始地址是1,不是4的倍数
} misaligned;
// 对齐的情况
struct {
char a; // 1字节
char pad[3]; // 填充3字节,让b对齐
int b; // 4字节,起始地址是4的倍数
} aligned;
在BEV模型中,特征图的通道数、宽度、高度,这些维度如果不对齐,访问效率会大打折扣。我见过一个案例,把特征图的宽度从31改成32(对齐到4的倍数),推理速度提升了15%。
实战技巧:在分配模型权重和特征图内存时,尽量对齐到Cache Line的大小(通常是64字节)。这样能最大化Cache利用率,减少Cache Miss。
嗯,内存对齐这块,看似是编译器的事,但在嵌入式部署中,我们经常要手动控制。因为编译器不知道你的硬件特性,它只能做通用优化。而咱们做部署的,就是要压榨出每一分性能。
小结
这一章我们聊了四个基础概念:
- SRAM和DRAM,一个快一个容量大,要合理搭配使用
- Cache层级,理解它才能写出高性能代码
- DMA传输,让CPU从数据搬运中解放出来
- 内存对齐,小细节决定大性能
这些概念,每个都值得你花时间去理解。别觉得基础,我做了这么多年部署,每次遇到性能瓶颈,回头一看,往往就是这些基础问题没处理好。下一章,我们开始讲BEV模型的内存布局,那才是真正考验功底的地方。
一句话总结:内存管理不是锦上添花,而是雪中送炭。基础打牢了,后面的优化才能事半功倍。