这一篇在干嘛?
论文出处:ACM Transactions on Embedded Computing Systems(TECS)2004 年第 3 卷第 2 期,作者 Chuanjun Zhang、Frank Vahid、Roman Lysecky,美国加州大学河滨分校。核心问题:在嵌入式微处理器系统里,访存(memory access)往往吃掉总功耗的一半左右,而”最优缓存配置”高度依赖当前正在跑的那个程序,没有任何一种固定配置能通吃。自调优思路:把原本跑在工作站上的 CAD 调优工具搬到芯片上,用一小块专用硬件在程序执行过程中自动搜索缓存的容量、行大小、相联度、路预测这四个参数。主要结论:相对一个未经调优的标准缓存,动态自调优平均节省约 40% 的访存总能耗(部分基准最高 70%),性能损失通常不到 2%;搜索平均只需检查 5.8 个(指令缓存)/ 5.4 个(数据缓存)配置,而穷举需要 27 个;调优器本身约 4000 门、面积只增加约 3%、一次完整搜索 164 个时钟周期、能耗仅 11.9 nJ。
一、背景:嵌入式系统里的缓存既香又费电
1.1 一半的电,花在了”搬数据”上
其中片上缓存处境尴尬:它单体功耗不算大,却是被访问最频繁的部件。每执行一条指令几乎都要取指,每做一次运算多半还要读一两个操作数。频率一高,积少成多。论文开篇就给出了一句被广泛引用的话:
Memory accesses often account for about half of a microprocessor system’s power consumption. (访存通常占微处理器系统功耗的一半左右。)
这里说的”访存功耗”不只是缓存本身的功耗,而是整条访存链路的功耗:片上缓存命中的开销、缓存缺失后访问片外存储器的开销、以及处理器在等数据时空转(stall)消耗的功耗。这一点很重要,后面我们会看到,它直接决定了”缓存是不是越大越好”这个问题的答案。
1.2 预制造平台的两难:想要定制,又不想流片
如果你做的是一颗专用芯片(ASIC,Application-Specific Integrated Circuit),事情很简单:先用仿真跑一遍你的应用,看它需要多大的缓存、什么行大小、什么相联度,然后把这个配置硬连线进芯片,流片出来就是最优的。这叫 core-based flow。
但现实里大量嵌入式产品用的是预制造平台(prefabricated platform)——买现成的微处理器 IP 或者现成的芯片,配上自己的软件和少量可配置逻辑。为什么?上市时间(time-to-market)。流片一次的成本和周期,对很多产品来说是承受不起的。
代价是:缓存大小这类关键的体系结构参数,在芯片出厂的那一刻就定死了,没法为你的应用定制。
芯片厂商当然可以提供”同一款 CPU、多种缓存配置”的产品线来覆盖不同需求,但在今天的**片上系统(SoC,System-on-Chip)**时代这条路越来越难走——一颗芯片上除了 CPU 还挤着一堆协处理器、可配置逻辑、外设,不可能为每种组合都出一个版本。
于是就有了本文的出发点:把关键的体系结构参数设计成”出厂后可配置”的,让设计者(或者芯片自己)能针对具体应用去配。
1.3 前人的四条路
论文引言梳理了此前几条有代表性的路线,值得一看,因为它们各自只解决了一个维度:
| 工作 | 可调的旋钮 | 报告的效果 |
|---|---|---|
| Motorola M*CORE [Malik et al. 2000] | 统一缓存的 4 个 way 可单独关断;还可配成指令 / 数据 / 统一缓存 | Powerstone 基准平均省 30% 总功耗,个别基准达 40% |
| Albonesi [1999] | 选择性关断 way(selective cache way) | Spec95 基准缓存能耗平均降 40% |
| Veidenbaum et al. [1999] | 行大小自适应调整 | 片外访存流量减少 50% 以上 |
| Balasubramonian et al. [2000] | 缓存可配成一级或两级层次 | 用作 L2/L3 时存储层次能耗省 43% |
可以看出一个共同点:每一家都只拧动了四个旋钮中的一个,而且大多靠监测某一个既定指标(失效率 miss rate、IPC、距上次访问的时间间隔、分支频率等)来单向地增大或减小那个参数。
本文要做的是:四个旋钮一起调(容量、行大小、相联度、路预测),并且自动调——不只是”可配”,而是”自己会配”。
1.4 调优这件事,为什么必须自动化
即使芯片给了你可配置的缓存,“找到最优配置”这一步仍然是个麻烦。传统做法有三条,每条都有硬伤:
- 手工试:设计者凭经验猜几组配置,逐个烧进去实测。慢,而且容易漏掉真正的最优。
- 仿真搜索:用周期精确仿真器(cycle-accurate simulator)跑。但搭建一个能准确建模外部环境(传感器输入、网络包、用户操作)的仿真平台,往往比建模系统本身还难;而且仿真极慢,跑几秒钟的应用行为可能要几十小时甚至几天。
- 用固定输入集仿真:这是第 2 条的隐藏陷阱。仿真时用的是一组固定的输入数据,而真实设备上数据是动态变化的。一个在”典型输入”下最优的配置,在另一组输入下可能完全不是最优。
更根本的是流程不匹配:嵌入式软件团队的标准工作流是”编译 → 下载 → 执行”,里面根本没有”先仿真搜索一通”这个环节。任何要求软件工程师先跑仿真的调优方法,推广起来都会碰壁。
所以本文提出了一句很精炼的主张:
We propose to move that CAD on-chip. (我们提议:把这套 CAD 搬到芯片上去。)
把调优硬件做成芯片上的一个小模块,让它在程序真正跑起来的时候,用真实数据、真实行为自动完成搜索。这就是”self-tuning(自调优)“这个名字的由来。
1.5 先睹为快:能省多少?
在展开细节之前,先看一眼最终的收益,好让你知道后面这些复杂设计值不值。下图是论文 Figure 3:对每一个 Powerstone / MediaBench 基准程序,把”为该程序单独调到最优的配置(cfg)“的访存能耗,与两种传统固定配置对比——cnv4w32(传统 4 路组相联、8 KB、行大小 32 字节)和 cnv1w32(传统直接映射、8 KB、行大小 32 字节)。所有数值都归一化到 cnv4w32。

图 3:各基准程序在”最优配置缓存(cfg)“下的访存能耗,对比传统 4 路 8 KB / 32 B 配置(cnv4w32)与传统直接映射 8 KB / 32 B 配置(cnv1w32)。能量按每个基准的 cnv4w32 值归一化。
一句话总结这张图:平均节能超过 40%,某些基准最高接近 70%;而且注意看那些”cfg 柱子”彼此之间的高度差异——对 crc 最优的配置和对 jpeg 最优的配置完全不是一回事,这正是我们下一节要深挖的”没有通吃配置”。
二、先复习:缓存的四个旋钮各管什么
如果你对缓存还不太熟,这一节先补基础;熟的话可以快速跳过公式部分,但建议看一下 2.5 的能耗模型,后面全篇都建立在它之上。
2.1 缓存凭什么有效:两种局部性
缓存(cache)能起作用,全靠程序访问存储器时表现出的两种局部性(locality):
- 时间局部性(temporal locality):刚被访问过的数据,很快还会被访问。比如循环计数器
i,一秒钟内被读上千次。 - 空间局部性(spatial locality):访问了地址 X,接下来多半会访问 X 附近的地址。比如顺序执行的指令流、顺序遍历的数组。
缓存就是”用一小块快而贵的 SRAM,兜住你最近用过的那一小撮数据”。命中(hit)就直接用,缺失(miss)就得去片外慢而便宜但很费电的存储器取。
衡量缓存好坏最基本的指标是失效率(miss rate),以及由此导出的平均访存时间(AMAT, Average Memory Access Time):
其中 是失效率, 是命中率, 是命中时的访问延迟, 是缺失时额外的惩罚(去片外取数据的往返时间)。
对应到能耗,我们更关心单次访存的平均能耗:
注意这里的 远大于 ——因为一次缺失要驱动片外的高电容总线、读写片外存储阵列,还要让处理器空转等待。典型数字可以到几十倍甚至上百倍的差距。这个巨大的不对称性,是后面所有权衡的根源。
2.2 旋钮一:总容量(cache size)
容量 越大,能装下的历史访问越多,**容量失效(capacity miss)**越少,失效率越低。
但代价也直接:容量翻倍,意味着每次访问要驱动的字线(word line)更长、位线(bit line)上挂的存储单元更多、译码器更大,于是每次访问的动态能耗上升;同时 SRAM 面积变大,静态漏电(leakage)功耗也线性上升。
2.3 旋钮二:行大小(line size)
行(line,也叫 cache block)是缓存与下一级存储器之间交换数据的最小单位。行大小 是空间局部性的直接杠杆:
- 大(如 64 B):一次缺失能把周围一大片数据都搬进来,对顺序访问极其友好,失效率下降明显。
- 大的代价:如果程序的数据访问很零散(比如链表遍历、哈希表查询、随机查表),你辛辛苦苦搬进来的 64 字节里,可能只有 4 字节被用上——剩下 60 字节白搬了,白白多耗了片外总线和片外存储器的能量。这叫过度取(over-fetch)。
所以行大小的最优值,本质上取决于”这个程序的空间局部性有多强”。而指令和数据的空间局部性通常差很多,这就是后面图 7 / 图 8 结论差异的伏笔。
2.4 旋钮三:相联度(associativity)
直接映射(direct mapped,即 1-way)缓存里,每个主存块只能放在唯一一个位置。两个恰好映射到同一位置、又都很常用的数据块,会互相把对方踢出去,来回颠簸——这叫冲突失效(conflict miss)。
组相联(set-associative)缓存把一个位置扩展成 个”路(way)“,一个块可以放在这 路中的任意一路,冲突就缓解了。
代价是:一次访问需要同时激活 路的标签阵列和数据阵列(并行比较 个标签),动态能耗大致随 增长。
一个常被忽略的事实:从 4 路加到 8 路,失效率的改善往往非常有限(因为 4 路时冲突失效已经基本消除了),但能耗却实打实地上去了。这正是本文启发式”见好就收”策略的依据。
2.5 旋钮四:路预测(way prediction)
路预测(way prediction)是针对组相联缓存的一个节能技巧:先只访问预测的那一路,猜对了就直接返回,省掉了激活其余 路的能量;猜错了才去访问剩下的路,代价是多花一个时钟周期。
论文引用了一组实用的经验数字:组相联指令缓存的路预测准确率大约 90%,数据缓存大约 70%。也就是说数据缓存的路预测有 30% 的时候会猜错,那个额外周期和额外能耗很容易把省下的部分吃回去。
后面你会看到,这个”看起来很美”的特性,在最终的实验结果里几乎没被选中——这是本文一个很诚实的发现。
2.6 缓存能耗模型(全文的核心公式)
现在把上面的定性分析定量化。论文用公式(1)计算访存相关的总能耗,它同时考虑了动态能耗、静态能耗、片外访问和处理器空转:
逐项拆解一下,因为理解这个公式就等于理解了本文的一半:
- :访问总次数 × 单次命中能耗。命中能耗取决于容量、行大小、相联度这些物理参数——配置越”豪华”, 越大。
- :缺失次数 × 单次缺失能耗。缺失次数由程序行为和配置共同决定。
- 被进一步拆成三项:片外存储器访问能耗 、处理器等待时空转的能耗 、以及把块填回缓存的能耗 。注意处理器空转也在耗电——这一点经常被初学者忽略。
- :静态漏电,等于总执行周期数 × 每周期静态能耗。注意这一项是通过”周期数”间接进入的:配置不好 → 缺失多 → 停顿多 → 周期数变多 → 静态能耗也变多。所以性能差的配置,静态能耗也会跟着变差。
这些参数怎么来的?论文是用 SimpleScalar(一个类 MIPS 的周期精确仿真器)跑出访问数和缺失数,用自己做的 0.18 μm CMOS 版图提取 (作者提到与 CACTI 模型的数值很接近),用三星的存储器手册取片外访问能耗,用 0.18 μm MIPS 处理器取空转能耗。
调优器(tuner)自己的能耗则用公式(2):
也就是”调优器功耗 × 运行时长 × 搜索次数”。后面我们会看到这个数值小到可以忽略——但如果搜索顺序设计得不好,光是缓存刷写(flush)的代价就能比它大几十万倍。
三、为什么没有一种配置能通吃所有程序
这一节回答本文最根本的问题:既然要调,为什么不干脆定一个”大家都还行”的固定配置?
3.1 容量不是越大越好:两条曲线的交叉
看论文 Figure 5:SPEC 2000 里的 parser 基准,把缓存容量从 1 KB 一路加到 1 MB,失效率的变化。

图 5:parser 基准在 1 KB 到 1 MB 缓存容量下的失效率变化。
曲线形状是典型的”陡降后趋于平坦”:从 1 KB 增到 16 KB,失效率急剧下降;再往上加,曲线几乎拉平了——因为 16 KB 已经装得下这个程序绝大部分的工作集(working set),再加容量也装不进更多有用的东西了。
再看 Figure 6,把能耗拆成三块画出来:片上缓存能耗、片外存储器能耗、以及两者之和(总能耗)。

图 6:parser 基准在 1 KB 到 1 MB 容量下,片上缓存能耗、片外存储器能耗与总能耗的分解。
这张图是全文最重要的一张,值得盯着看三遍:
- 片外存储器能耗曲线:一路下降(因为失效率降了),但在 16 KB 之后几乎不再下降。
- 片上缓存能耗曲线:一路上升(因为每次访问要驱动更大的阵列,漏电也更多)。
- 总能耗曲线:先降后升,呈一个U 形,最低点正好在两条曲线的”胜负手”附近。对
parser来说,这个转折点就在 16 KB——过了这个点,再加大容量,性能几乎不涨,能耗却明显上升。
这就是”越大越好”直觉破产的地方。缓存调优的本质,就是找这个 U 形曲线的谷底。
而关键来了:
Unfortunately, this tradeoff point is different for every application and exists not only for cache size but also for cache associativity and line size. (不幸的是,这个权衡点对每一个应用都不一样,而且它不仅存在于容量维度,也存在于相联度和行大小维度。)
3.2 一个具体的小例子:为什么不同程序的口味差这么多
设想三个小程序,同样跑在一颗嵌入式处理器上:
程序 A:一个 FIR 滤波器(对应基准 fir)
核心是一个系数数组 coef[N] 和一个滑动窗口,内层循环顺序遍历。顺序访问 = 极强的空间局部性。对它,大行大小(64 B)很划算,一次取进来的 64 字节后面马上全用得上。但它的数据总量很小(几十个系数加一个窗口),大容量没用——给它 8 KB 纯属浪费,白白多付了每次访问的能耗。
程序 B:一个 CRC 校验(对应基准 crc)
按字节流式处理一段数据,外加一张 256 项的查表。代码量和数据量都极小,工作集可能只有几百字节。对它,2 KB 直接映射就够了,8 KB 的巨大阵列每次访问都在白白烧电。查表访问比较随机,空间局部性一般。
程序 C:一个 JPEG 解码(对应基准 jpeg)
代码量大、跳转多、有多个大块数据缓冲区,还存在不少映射到同一组的冲突。对它,8 KB 四路组相联才是白菜。
如果芯片只能给一个固定配置,你只能选”三者都不太满意”的折中;而如果缓存可调,A 拿 2K_1W_64B、B 拿 2K_1W_32B、C 拿 8K_4W_32B——每个程序吃到自己那口。后面表 1 的实测数据会证明,这些配置确实五花八门。
3.3 实测:四个参数到底谁影响大
论文做了一件很扎实的事:把 13 个 Powerstone 基准 + 6 个 MediaBench 基准,在全部 27 种可行配置下都跑了一遍,然后分析每个参数的影响权重。
先说明为什么是 27 而不是 :因为容量是靠关断 way 实现的,所以有些组合物理上不存在——比如”4 路但只有 2 KB”就不可能(4 路 × 每路 2 KB = 8 KB 是基础配置,2 KB 只能由 1 路构成)。合法组合数是 27。
分析方法很朴素:固定三个参数,只变第四个,看能量条怎么动。
指令缓存(Figure 7,上图是平均指令失效率,下图是归一化取指能耗):

图 7(上):各基准的平均指令缓存失效率。总容量标注为 8 / 4 / 2 KB,行大小为 16 / 32 / 64 B,相联度为 1 / 2 / 4 路。

图 7(下):对应的归一化取指能耗。注意容量带来的能量差异可以达到 2 倍以上,而行大小与相联度带来的差异很小。
数据缓存(Figure 8,同样上图失效率、下图能耗):

图 8(上):各基准的平均数据缓存失效率。

图 8(下):对应的归一化数据读取能耗。与指令缓存不同,这里行大小带来的能量差异明显变大了,尤其在 2 KB 容量时。
从这两组柱状图,可以读出三条非常实用的结论:
- 总容量的影响最大。看每组柱子之间的高度差,改容量能让能耗差到 2 倍甚至更多。所以它是最该优先调对的参数。
- 行大小对指令缓存影响很小,对数据缓存影响明显(尤其在小容量 2 KB 时)。原因很直白:指令地址天然是顺序的,空间局部性极强,16 B 和 64 B 的差别被”反正后面都要用”抹平了;而数据地址的空间局部性弱得多,链表、指针跳转、随机查表都会让大行变成浪费。
- 相联度对能耗的影响最小。看同一种颜色的柱子在 1/2/4 路之间的变化,幅度远小于容量和行大小。
这三条直接决定了下一节启发式的搜索顺序:先定容量 → 再定行大小 → 再定相联度 → 最后决定要不要开路预测。影响大的先定,影响小的后定。
四、可配置缓存是怎么在硬件上实现的
知道了”要调什么”,接下来的问题是:怎么让一块出厂的硅片支持这些配置变化? 论文给出的答案出人意料地简单,这也是它能落地的关键。
4.1 相联度可变:路拼接(way concatenation)
基础缓存是 4 个 bank(体),默认当 4 路组相联用。所谓路拼接,就是通过配置一个小寄存器,把这 4 路**“拼接”**成 2 路,甚至拼成 1 路(直接映射)。

图 1:可路拼接(way-concatenable)的四路组相联缓存结构,图中标出了关键路径(critical path)。
具体做法:配置电路输出 四个信号,分别控制四路的开与关。实现上,只把传统缓存里的两个反相器换成 NAND 门——一个是字线驱动器(word line driver),另一个是比较器的输出反相器。整个配置逻辑只有 8 个逻辑门。
作者在自己 0.18 μm CMOS 的版图上验证过:通过合理调整这两个 NAND 门的尺寸,可以做到完全不影响缓存的关键路径。面积开销相对整个缓存可以忽略。
这里有一个设计取舍值得记住:即使配成直接映射,电路仍然检查完整标签(full tag,地址位 到 ),而不是像某些设计那样把标签缩到 2 位。作者指出,缩标签省不了多少电,却会让控制电路复杂化;更关键的是,保留完整标签检查是后面避免缓存刷写的前提(见 4.4 节)。
4.2 容量可变:路关断(way shutdown)
容量调节走的是另一条路:直接关掉某些 way。关掉一半 way,容量就减半——8 KB 的 4 路里关掉 2 个 way,就变成 4 KB;再关,就得到 2 KB。
为什么关 way 能省电? 两个层次:
- 动态功耗:被关掉的 way,其标签阵列和数据阵列不再被激活,字线不再充放电,比较器不再翻转。从 4 路降到 1 路,粗略地说每次访问只驱动原来 1/4 的阵列。
- 静态功耗:关断逻辑用**睡眠晶体管(sleep transistor)**切断被关 way 的电源,直接压掉漏电。这在纳米工艺下越来越重要(论文引用 Agarwal 等人的工作指出静态功耗占比正在上升)。
代价是什么? 显而易见:失效率上升。容量减半意味着工作集更容易装不下,冲突也更频繁。如果失效率涨得太多,省下的片上缓存能耗会被片外访存能耗加倍讨回去——这正是公式(1)在算的账。
硬件开销:关断逻辑让关键路径增加约 5%,面积增加不到 1%。
顺带说一句,一个直观类比:大停车场有 4 层,每次找车都要把 4 层灯全打开;只停了 1/4 的车时把另外 3 层灯关掉,电费立刻降下来——代价是偶尔停进去要多花时间(多一次缺失)。
4.3 行大小可变:行拼接(line concatenation)
行大小的配置思路很巧:物理行大小固定为 16 字节,更大的行(32 B、64 B)在逻辑上由多个物理行拼成。

图 2:行大小可配置缓存的结构。物理行固定为 16 字节,32 / 64 字节的行由多个物理行逻辑拼合而成。
设计者只需在缓存控制器里配一个小寄存器,就能在 16 / 32 / 64 字节之间切换。
这个”物理固定 + 逻辑拼合”的做法有个额外的好处:切换行大小时不会引入额外的缺失,也不需要刷写(原因见 4.4 节)。这也是为什么后面行大小可以被放在搜索顺序的任意位置。
4.4 路预测的硬件位置
路预测只在多路组相联时有意义,直接映射(1 路)下永远是关闭状态。硬件上就是”先访问一路、不中再访问其余路”的控制逻辑,代价是猜错时多一个周期。
五、自调优:怎么在几分钟内找到接近最优的配置
这是本文的方法论核心。目标很明确:自动地、透明地、动态地把缓存调到当前执行的程序上,而且调优过程和附加硬件要尽可能小、省电、不影响性能。
5.1 先把调优硬件挂上去
整体连接关系见 Figure 4:一小块额外硬件(cache tuner)挂在处理器和缓存之间,可以由软件使能或禁用。

图 4:缓存自调优硬件的整体结构。调优器由软件控制开关,运行时自动搜索并配置缓存参数。
什么时候触发调优?论文明确说这是与自调优架构本身正交的选择,可以:
- 只在软件显式进入的”调优模式”里跑一次;
- 在任务启动时跑;
- 检测到**程序阶段变化(phase change)**时跑;
- 或者每隔固定周期跑一次。
这种正交性设计很聪明:它让同一套硬件既能用于”出厂时配一次就锁死”的保守场景,也能用于”跟着程序阶段动态调整”的激进场景。
5.2 朴素穷举的两个致命问题
最朴素的做法:把所有配置按某个顺序试一遍,每个配置测一下失效率、估算能耗,最后选能耗最低的。这条路有两个硬伤:
问题一:配置数量爆炸。 本文的缓存只有 27 种配置,还能穷举。但只要每个参数的取值多几个,配置数就轻易破百。更要命的是系统里不止缓存一个部件可配——二级缓存、总线、甚至处理器本身都可能有参数。如果联合调优,配置数是相乘的:
一百万种配置,穷举是不可想象的。
问题二:缓存刷写(cache flush)极其昂贵。 每换一次配置,如果不清空缓存,新配置下缓存里的数据可能”查不到”或”查错”,导致结果错误。所以朴素做法每次换配置都要 flush。
对数据缓存尤其糟:被关掉的 way 里如果有脏数据(dirty data),必须先写回(write back)到片外存储器。这既费时又费电。后面 6.4 节会给出一个吓人的数字:如果按”从大到小”的顺序搜索容量,光是刷写回写的额外能耗就平均达到 5.38 mJ,是我们整个调优器能耗的 48 万倍。
所以启发式的设计目标被明确为两条:
- 最小化被检查的配置数量;
- 最小化(甚至消除)缓存刷写。
5.3 参数排序:谁影响大,先调谁
从 3.3 节的实测分析已经得出影响权重:容量 ≫ 行大小(数据缓存)> 相联度。所以启发式的参数顺序定为:
容量 → 行大小 → 相联度 → 路预测
而且每个参数都采用同一个策略:从最小值开始,只要继续增大还能让总能耗下降,就继续增大;一旦不再下降,立刻停在当前这一档(见好就收)。
这个”见好就收”是有依据的——回顾图 6 的 U 形曲线:总能耗先降后升,所以第一次出现”不再下降”的时候,通常就已经在谷底附近了。
5.4 取值顺序:从大到小还是从小到大
光定好”先调容量”还不够,同一个参数内部先试哪个值,会决定要不要 flush。这才是最精妙的部分。
容量:必须从小到大。 见 Figure 9。

图 9:容量配置时的缓存刷写分析。增大容量不需要刷写,减小容量则需要刷写(图中标签宽度固定为 4 位)。
为什么?关键在于索引(index)宽度会随配置变化,而标签宽度是固定的。
- 减小容量时:原来映射到不同位置的两个地址,缩小后会撞到同一个块。论文的例子:地址
00000(index000,tag0000)和00100(index100,tag0010)在缩小前都是命中,缩小后却都被映射到 index00的同一个块——一个原本的命中变成了缺失,而且可能造成数据错乱。对数据缓存,被关掉 way 里的脏数据必须先写回,代价高昂。 - 增大容量时:原来在同一个块里的地址会被分散到更多块中,最多多一次缺失(要重新取一次),但不需要写回、不需要 flush。
两害相权:多几次缺失,远比 flush + 写回便宜。所以从小到大。
相联度:同样是从小到大。 见 Figure 10。

图 10:相联度增减时的缓存刷写分析(标签宽度固定为 4 位)。
- 增大相联度:只是把更多的 way 激活来读数据,不会产生额外缺失,也不会出错。原来命中的地址增大后依然命中(因为数据还在那些 way 里,只是现在能看到的 way 更多了)。
- 减小相联度:可能把命中变成缺失。论文的例子:地址
00000在 bank 3 中命中,如果相联度降到直接映射,缓存会去 bank 0 找它——找不到,变成缺失。
两种方向都不需要 flush,前提是前面提到的设计决策:始终检查完整标签。如果我们像某些设计那样在直接映射模式下把标签缩到 2 位,那么减小相联度时旧数据就会”看起来”是新的,导致静默的数据错误。这个”多花一点点标签比较功耗,换来完全不需要 flush”的取舍,是整个自调优方案能成立的地基。
行大小:增大或减小都一样。 因为物理行固定为 16 字节,改行大小只是改逻辑拼合方式,既不会产生额外缺失,也不需要刷写。
5.5 完整的搜索启发式
把上面的分析汇总,就得到 Figure 11 的伪代码。先约定输入:
- 容量 ,,本文 : KB, KB, KB;
- 相联度 ,,本文 : 路, 路, 路;
- 行大小 ,,本文 : B, B, B;
- 路预测 ,。
Cache Tuning Heuristic Algorithm
Input: cache size C[i], associativity A[i], line size L[j], way prediction W[1]=off, W[2]=on
Output: 最优的容量 C、相联度 A、行大小 L 与路预测状态 W
begin
A = A[1]; L = L[1]; W = W[1]; E[0] = 0
// 第一步:从小到大定容量
for i = 1 to n do
用公式(1)计算: E[i] = f(C[i], A, L, W)
if E[i] < E[i-1] then break
end
C_best = C[i]; E[1] = E[i]
// 第二步:从小到大定行大小
for j = 2 to p do
用公式(1)计算: E[j] = f(C_best, A, L[j], W)
if E[j] < E[j-1] then break
end
L_best = L[j]; E[i] = E[j]
// 第三步:从小到大定相联度
for k = 2 to m do
用公式(1)计算: E[k] = f(C_best, A[k], L_best, W)
if E[k] < E[k-1] then break
end
A_best = A[k]; E[0] = E[k]
// 第四步:只有多路时才考虑路预测
if A_best = 1 then
W_best = W[1]
else
W = W[2]
if E[1] = f(C_best, A_best, L_best, W) < E[0] then
W_best = W[2]
output: C_best, A_best, L_best, W_best图 11:确定最优缓存配置的搜索启发式(伪代码)。
用自然语言复述一遍这个流程,确认你理解了每一步的意图:
- 起点:2 KB、1 路(直接映射)、16 B 行、关闭路预测——所有参数都取最小值。这样后面每一步都是”增大”,天然规避 flush。
- 第一步(容量):依次试 2 KB → 4 KB → 8 KB,每试一档用公式(1)算总能耗。只要还比上一档低就继续;一旦不降了,就停在上一档(伪代码里
break后取当前 )。 - 第二步(行大小):固定刚定好的容量,依次试 16 B → 32 B → 64 B,同样的”见好就收”逻辑。
- 第三步(相联度):固定容量和行大小,依次试 1 路 → 2 路 → 4 路,同样逻辑。
- 第四步(路预测):如果最终选中的是 1 路(直接映射),路预测无意义,直接关掉;否则试着打开路预测,算一次能耗,比原来低就开,否则关。
5.6 搜索规模:从指数爆炸到线性
这个启发式的效率可以做一般性分析。假设有 个可配参数,每个参数有 个取值,且取值相互独立:
看两个论文给出的具体例子:
- 10 个参数、每个 10 个取值:穷举要搜 种(一百亿),启发式最多搜 种。差了一亿倍。
- 两级缓存层次:一个 16 KB 8 路的一级指令 + 数据缓存(行大小 8/16/32/64 B),外加一个 256 KB 8 路的二级统一缓存(行大小 64/128/256/512 B)。总的解空间是 种;用启发式最多只需搜 种。
代价是什么?启发式不保证找到全局最优。这是一个典型的”用一点点最优性换巨大的搜索代价下降”的工程取舍,后面 6.3 节会量化这个损失有多小。
六、把启发式做成硬件:片上调优器
6.1 为什么不用软件实现
一个自然的疑问:这个搜索逻辑看起来不复杂,写成一段软件跑在系统处理器上不就行了?
论文明确否掉了,理由是自我干扰:如果让系统处理器来执行搜索程序,那么这段搜索代码本身就会:
- 改变应用的运行时行为(占用了 CPU 时间);
- 污染缓存行为(搜索代码自己的指令和数据会进缓存,把应用的块挤出去,改变失效率)。
第 2 点是致命的——你测出来的失效率,是”应用 + 搜索代码”的失效率,不是应用自己的。这会导致搜索选到一个非最优的配置。
所以必须用独立的硬件:它不占用处理器的取指带宽,也不进缓存,测量的就是纯粹的应用行为。
6.2 数据通路:18 个寄存器就够
实现方式是一个状态机 + 数据通路(FSM+D, Finite State Machine plus Datapath),见 Figure 12。

图 12:缓存调优器的 FSM+D 结构。“input” 包含时钟、复位和 start 信号;“control” 是 FSM 输出、用于控制寄存器和多路选择器;数据通路中比较器的输出反馈回 FSM。
数据通路里只有 18 个寄存器,分工如下:
| 用途 | 数量 | 位宽 | 说明 |
|---|---|---|---|
| 收集运行时信息 | 3 | 16 | 总命中数、总缺失数、总周期数 |
| 存各配置的单次命中能耗 | 6 | 16 | 8 KB 的 4/2/1 路、4 KB 的 2/1 路、2 KB 的 1 路 |
| 存各配置的单次缺失能耗 | 3 | 16 | 对应 16 / 32 / 64 B 三种行大小 |
| 存静态功耗 | 3 | 16 | 对应 8 / 4 / 2 KB 三种容量(静态功耗只取决于容量) |
| 能耗计算结果 | 1 | 32 | 当前配置的能耗 |
| 当前最优能耗 | 1 | 32 | 已测配置中的最低能耗 |
| 配置寄存器 | 1 | 7 | 4 个参数:容量/行大小/相联度各 3 值(2 bit × 3)+ 路预测 1 bit |
有两个细节很能体现设计的紧凑:
- 命中能耗只需要 6 个寄存器,而不是 个。因为物理行固定为 16 B,不同行大小下的命中能耗是相同的——行大小只影响缺失能耗(一次要搬多少字节)。所以命中能耗只需按”容量 × 相联度”的 6 种合法组合存。
- 静态功耗只与容量有关,所以 3 个寄存器就够,不用管相联度和行大小。
FSM 通过 control 信号控制寄存器与多路选择器(mux),数据通路里比较器的输出(当前能耗是否小于最优能耗)反馈给 FSM 作为状态转移条件。乘法器只有一个,所以三次乘法要分时进行——这直接催生了下一节的第三级状态机。
6.3 三级状态机

图 13:缓存调优器的状态机结构。实线为三个状态机各自的状态转移,虚线表示上层状态机对下层状态机的依赖(例如 P1 必须等 VSM 跑完才能进入 P2)。
整个 FSM 由三个小型状态机嵌套组成,这个结构非常值得学习:
-
参数状态机 PSM(Parameter State Machine):管”现在调哪个参数”。
- 起始态:等待
start信号; P1:调容量;P2:调行大小;P3:调相联度;P4:调路预测。
- 起始态:等待
-
取值状态机 VSM(Value State Machine):管”这个参数现在试第几个值”。因为任何参数的取值最多 3 个,所以用 4 个状态(
V0是与 PSM 的接口态,V1/V2/V3对应三个取值)。巧妙之处在于状态的含义由当前 PSM 状态决定:- 若 PSM 在
P1,则V1/V2/V3分别对应 2 KB / 4 KB / 8 KB; - 若 PSM 在
P2,则分别对应 16 B / 32 B / 64 B; - 以此类推。
这种”状态复用”让硬件极其紧凑——不用为四个参数各建一套取值状态。
- 若 PSM 在
-
计算状态机 CSM(Calculate State Machine):管”这次能耗怎么算”。因为有三次乘法但只有一个乘法器,所以用一个 4 状态的状态机分时完成,第一个状态同样是与 VSM 的接口态。
虚线箭头表达的依赖关系很重要:上层必须等下层跑完才能推进(P1 必须等 VSM 遍历完三个取值,才能进 P2)。
七、实验结果:省了多少电,性能掉多少
7.1 实验设置
- 仿真器:SimpleScalar(周期精确,含类 MIPS 处理器模型);
- 基准集:13 个 Motorola Powerstone 基准 + 6 个 MediaBench 基准;
- 能耗参数: 来自作者自己 0.18 μm CMOS 的可配置缓存版图(与 CACTI 数值接近), 来自三星标准存储器, 来自 0.18 μm MIPS 处理器;
- 调优器参数:VHDL 实现,Synopsys Design Compiler 综合。
7.2 搜索结果:绝大多数命中全局最优
下表是论文 Table I 的完整结果(19 个基准)。各列含义:Ben. 基准名,I-cache cfg. / D-cache cfg. 选出的指令 / 数据缓存配置,No. 该次搜索实际检查的配置数,E% 指令与数据缓存合计的能耗节省比例(相对标准未调优缓存)。
配置命名规则:8K_1W_64B = 8 KB 容量、1 路(直接映射)、64 字节行;末尾带 _P 表示启用路预测。
| 基准 | 指令缓存配置 | 检查数 | 数据缓存配置 | 检查数 | 节能 |
|---|---|---|---|---|---|
| padpcm | 8K_1W_64B | 7 | 8K_1W_32B | 7 | 23% |
| crc | 2K_1W_32B | 4 | 4K_1W_64B | 6 | 70% |
| auto | 8K_2W_16B | 7 | 4K_1W_32B | 6 | 3% |
| bcnt | 2K_1W_32B | 4 | 2K_1W_64B | 4 | 70% |
| bilv | 4K_1W_64B | 6 | 2K_1W_64B | 4 | 64% |
| binary | 2K_1W_32B | 4 | 2K_1W_64B | 4 | 54% |
| blit | 2K_1W_32B | 4 | 8K_2W_32B | 8 | 60% |
| brev | 4K_1W_32B | 6 | 2K_1W_64B | 4 | 63% |
| g3fax | 4K_1W_32B | 6 | 4K_1W_16B | 5 | 60% |
| fir | 4K_1W_32B | 6 | 2K_1W_64B | 4 | 29% |
| jpeg | 8K_4W_32B | 8 | 4K_2W_32B | 7 | 6% |
| pjpeg | 4K_1W_32B | 6 | 4K_1W_16B | 5 | 51% |
| (pjpeg 全局最优) | — | — | 4K_2W_64B | — | — |
| ucbqsort | 4K_1W_16B | 6 | 4K_1W_64B | 6 | 63% |
| tv | 8K_1W_16B | 7 | 8K_2W_16B | 7 | 37% |
| adpcm | 2K_1W_16B | 5 | 4K_1W_16B | 5 | 64% |
| epic | 2K_1W_64B | 5 | 8K_1W_16B | 6 | 39% |
| g721 | 8K_4W_16B_P | 8 | 2K_1W_16B | 3 | 15% |
| pegwit | 4K_1W_16B | 5 | 4K_1W_16B | 5 | 37% |
| mpeg2 | 4K_1W_32B | 6 | 4K_2W_16B | 6 | 40% |
| (mpeg2 全局最优) | — | — | 8K_2W_16B | — | — |
| 平均 | — | 5.8 | — | 5.4 | 45% |
从这张表能读出五件事:
(1)搜索代价低得惊人。 平均只检查 5.8 个(指令缓存)/ 5.4 个(数据缓存)配置,而穷举需要 27 个。省掉了大约 79% 的搜索。
(2)几乎没有最优性损失。 19 个基准里,只有 mpeg2 和 pjpeg 两个的数据缓存没选到全局最优,分别比最优差 5% 和 12%。其余全部命中。
为什么会失手?论文解释得很清楚,值得细读:以 mpeg2 为例,在 1 路 16 B 行配置下,数据缓存失效率是 4 KB 时 0.82%、8 KB 时 0.58%——只降了约 1.4 倍(相对降幅)。按公式(1),这个降幅带来的片外能耗节省不足以抵消 8 KB 缓存自身多出来的能耗,于是启发式在容量这一步就停在了 4 KB。
但问题是:如果后面把相联度加到 2 路,8 KB 的失效率会显著下降(约 5 倍降幅),这时候 8 KB 才是划算的。而启发式在做”容量”这一步时,无法预知后面调相联度会发生什么。这就是贪心(greedy)策略的经典弱点:参数之间存在交互作用,逐个贪心会漏掉跨参数的协同最优。
(3)最优配置确实是”一程序一配置”。 看数据缓存那一列:有 8K_1W_32B,也有 2K_1W_16B;有 4K_2W_32B,也有 8K_2W_16B。容量从 2 KB 到 8 KB、行从 16 B 到 64 B、相联度从 1 路到 4 路,全都出现过。这从实测上坐实了”没有通吃配置”。
(4)绝大多数情况下,直接映射(1 路)就是最优。 这是最反直觉、也最有价值的一条结论。为什么?因为能耗账和性能账算出来的答案不一样:单纯追求性能的话,4 路当然失效率更低;但把”每次访问要多驱动 4 套阵列”的能耗算进去之后,直接映射的低廉单次访问能耗往往赢了。这是一个非常好的提醒——低功耗设计里,“性能指标最优”和”能耗指标最优”经常不是同一个配置。
(5)路预测几乎没用。 只有 g721 一个基准的指令缓存选中了带 _P 的配置(8K_4W_16B_P)。论文给出的解释很实在:路预测只在组相联下可用,而能耗最低的配置大多是直接映射,根本轮不到路预测出场。这也印证了 2.5 节提到的:数据缓存路预测准确率只有约 70%,猜错的额外周期很容易把收益吃光。
7.3 换个搜索顺序会怎样
为了证明”容量 → 行 → 相联度 → 路预测”这个顺序不是随便定的,论文试了一个对照顺序:行大小 → 相联度 → 路预测 → 容量(把影响最大的容量放到了最后)。
结果明显变差:指令缓存有 18 个例子中的 11 个没找到最优配置,数据缓存有 18 个例子中的 7 个没找到最优。两种缓存下,次优配置最多多耗 7% 的能量。
这个对照实验是本文方法论证的关键一环:参数顺序不是拍脑袋,而是由图 7 / 图 8 的实测影响权重决定的,并且有量化验证。
7.4 调优器自己的开销:小到可以忽略
| 指标 | 数值 |
|---|---|
| 面积 | 约 4,000 门,即 0.039 mm²(0.18 μm CMOS) |
| 相对 MIPS 4Kp(含缓存)的面积增加 | 略高于 3% |
| 完成一次完整搜索的周期数 | 164 个周期 |
| 功耗(200 MHz 下) | 2.69 mW |
| 一次搜索的平均能耗(平均搜 5.4 个配置) | 11.9 nJ |
| 对比:各基准的访存总能耗 | 0.16 mJ ~ 3.03 J,平均 2.34 J |
把 11.9 nJ 和平均 2.34 J 放一起看:调优器能耗约为总能耗的 五十亿分之一(),完全可以忽略。
164 个周期是什么概念?在 200 MHz 下不到 1 微秒。所以”自调优”这件事在时间上几乎是免费的——你可以在任务切换时甚至周期性地重跑,而不用担心开销。
7.5 反例警示:如果顺序搞反,flush 会吃掉一切
论文做了一个很有冲击力的对照计算:如果按从大到小(8 KB → 2 KB)的顺序搜索容量,那么每次缩小都要把被关 way 里的脏数据写回。实测下来,平均每个基准因写回脏数据多消耗 5.38 mJ。
拿它和调优器自己的 11.9 nJ 比:
论文的说法是”约 480,000 倍”(按各自的取整方式略有出入,量级一致)。
这个数字的冲击力在于:一个看起来只是”遍历顺序”的小决定,能让代价差出五个数量级。这也再次说明 5.4 节那些关于”从小到大”的分析不是学究式的讲究,而是整个方案能否落地的生死线。
7.6 性能损失:不到 2%
最后回到那个必须回答的问题:省了这么多电,性能掉多少?
论文的结论(2.6 节):虽然某些配置会导致性能下降,但可配置缓存带来的性能开销通常小于 2%。
结合 7.2 节的数据可以理解为什么:省电的来源主要是”把缓存缩到刚好够用”,而缩容量确实会增加失效率 → 增加停顿 → 性能下降;但因为启发式是用公式(1)的总能耗作为目标函数,而公式(1)里已经通过 这一项隐含地对性能做了惩罚(周期数变多,静态能耗也涨),所以搜索天然会避开”省了电但慢得离谱”的配置。
这就是把能耗模型建全的好处:你不需要显式地加一个性能约束,模型自己就会替你把关。
补充:能耗-延迟积(EDP)
如果你想更显式地把性能和能耗放在一起权衡,业界常用能耗-延迟积(EDP, Energy-Delay Product):
EDP 越小,表示”又快又省”。注意本文并没有直接用 EDP 作为目标函数,而是通过公式(1)里的静态能耗项间接考虑了执行时间。补充在这里,是因为你在读其他低功耗论文时会反复遇到它。
八、局限与启发:怎么用到自己的项目
8.1 这篇论文的局限(论文自己也很诚实)
- 贪心策略会漏掉跨参数协同最优。mpeg2 和 pjpeg 的失手已经证明了这一点:先定容量时无法预知后面调相联度的效果。损失是 5% 和 12%,可以接受,但不是零。
- 只在小缓存上验证过。本文的缓存是 2/4/8 KB。作者明确说,虽然启发式能扩展到更大、取值更多的缓存,但更大缓存下的启发式精度尚未分析,列为未来工作。
- 没有片上的解析能耗模型。论文指出,现有的 CACTI 只建模”单次访问的能耗”,而”某个具体应用在某配置下的总能耗”必须靠仿真拿失效率和访问次数。要在片上动态预测性能和能耗,还需要新的解析模型——这也是未来工作。
- 工艺背景是 2004 年的 0.18 μm。今天静态漏电占比已远超当年,虽然本文方法(尤其 way shutdown 用睡眠晶体管压漏电)在深亚微米下反而更有价值,但具体数字需重新标定。
- 未涉及多级缓存与多核。论文讨论了两级缓存的可能性(5.6 节),但实验只覆盖了一级指令/数据缓存。
8.2 可以迁移到自己项目的思路
即便你不做芯片设计,这篇 2004 年的论文里有几条方法论是通用的:
(1)“物理固定 + 逻辑拼合”是一种低开销实现可配置性的通用套路。 行大小可变的实现(物理 16 B,逻辑拼成 32/64 B)只改了控制逻辑,没动存储阵列。这种”底层保持规整、上层做逻辑映射”的思路,在可配置硬件、可重构加速器、甚至软件里的内存池设计里都能用。
(2)先量化影响权重,再决定优化顺序。 本文没有一上来就设计搜索算法,而是先跑了 27 种配置的全组合实验,用柱状图确认了”容量 ≫ 行大小 > 相联度”,才定下搜索顺序,并且用对照实验(7.3 节)验证了这个顺序确实更优。先测量、再优化,这句老话在这里有具体的数字支撑。
(3)遍历顺序本身就是一项重要设计。 “从小到大还是从大到小”看起来是无关紧要的实现细节,实际上决定了要不要 flush,代价差了 48 万倍。做任何增量式重配置(重新编译、重新分区、热更新)时,都值得问一句:有没有一种顺序能让中间状态不需要”推倒重来”?
(4)测量工具本身不能污染被测对象。 6.1 节否掉软件实现的核心理由是”搜索代码会改变缓存行为”。这是观察者效应在计算机系统里的典型例子。做性能剖析(profiling)时同样要警惕:你的探针(probe)本身改变了被观测的行为。
(5)把”代价”完整地写进目标函数。 公式(1)之所以有效,是因为它把片外访问、处理器空转、静态漏电全都算了进去,而不是只盯着”缓存自己的能耗”。如果目标函数建窄了,优化就会走向错误的方向——比如一味缩小容量,最后片外访存爆炸。优化目标的边界划在哪里,往往比优化算法本身更决定成败。
8.3 一点延伸思考
这篇论文是”on-chip CAD”(把 EDA 工具搬到芯片上)这个研究方向的一个早期代表作。它的野心不止于缓存:既然缓存能自调优,那总线宽度、二级缓存、电压频率、甚至处理器微结构,理论上都可以自调优。论文在 5.6 节对”多参数联合调优”的规模分析( vs )就是在为这个更大的愿景铺路。
站在今天回看,这个方向与后来兴起的自适应运行时系统(autonomic computing)、**动态电压频率调节(DVFS)**其实一脉相承。
常见坑
- 坑 1:以为”缓存越大越省电”。 恰恰相反——大容量降低失效率(省片外能耗),但抬高每次访问的片上能耗与漏电。总能耗是 U 形曲线,谷底因程序而异(图 6)。
- 坑 2:只看失效率,不看能耗。 4 路组相联的失效率一定比直接映射低,但本文实测中能耗最低的配置大多是直接映射,因为每次访问多驱动 3 套阵列的代价更贵。性能优化和能耗优化的最优解经常不是同一个。
- 坑 3:以为行大小对指令和数据的影响一样。 指令地址空间局部性极强,行大小影响很小;数据地址弱得多,行大小影响明显(对比图 7 与图 8)。照搬一套参数会翻车。
- 坑 4:忽略缓存刷写的代价。 缩小容量需要写回脏数据,平均 5.38 mJ;而精心设计的”从小到大”顺序完全避开了它。方向选错,代价差 5 个数量级。
- 坑 5:用被观测系统自己来观测自己。 把搜索程序跑在系统处理器上,它自己的指令和数据会进缓存、改变失效率,导致选出错误配置。测量工具必须与被测量对象隔离。
- 坑 6:目标函数建窄了。 只算缓存自身能耗会误导优化。必须像公式(1)那样把片外访问、处理器空转、静态漏电全算进去——顺带一提,把 算进去还”免费”地抑制了性能恶化。
通关标准
- 能说出缓存的容量、行大小、相联度各自影响什么:容量主要影响容量失效与单次访问能耗;行大小是空间局部性的杠杆,过大导致过度取;相联度缓解冲突失效,但每次访问需并行激活多路,能耗随路数增长。
- 能解释为什么不同程序的最优缓存配置差别很大:因为总能耗对容量/行大小/相联度都呈 U 形,而谷底位置取决于程序的工作集大小和空间局部性强弱,这两个属性因程序而异。
- 能说出way shutdown 为什么省电、代价是什么:关断的 way 不再被激活(省动态功耗),睡眠晶体管切断电源(省静态漏电);代价是容量下降导致失效率上升,可能把省下的电加倍还给片外访存。
- 能复述自调优启发式的四步顺序及其依据:容量 → 行大小 → 相联度 → 路预测(按实测影响权重排序),每步”从小到大、见好就收”(规避 flush 且贴合 U 形曲线)。
- 能看懂本文”省了多少电、性能掉多少”的权衡数据:平均省约 40%(表 1 平均 45%,最高 70%),性能损失通常不到 2%;搜索从 27 个配置降到平均 5.8 / 5.4 个,19 个基准中仅 2 个未达全局最优(差 5% 和 12%)。
自测 1:为什么"缓存容量越大越省电"是错的?请用公式(1)的哪一项来解释。
答案要点:公式(1)中 。容量增大 → 下降(片外能耗 那一块变小),但 上升(更大阵列 → 更长字线/位线、更多漏电),且 也上升。当”片上增量”超过”片外减量”时,总能耗反而上升。所以总能耗是 U 形曲线,图 6 中
parser的谷底在 16 KB。
自测 2:启发式为什么要求"从小到大"调容量和相联度?其中哪一个方向会真正导致缓存刷写?
答案要点:只有减小容量会导致刷写。减小容量时索引变窄,原本映射到不同块的地址会撞到同一块,被关掉 way 里的脏数据必须写回(图 9)。增大容量最多多一次缺失,但不需要写回(图 9)。相联度则两个方向都不需要 flush(图 10),但减小相联度会把命中变缺失,所以仍选”从小到大”。这个”不需要 flush”的前提是始终检查完整标签(full tag),否则缩小相联度会造成静默数据错误。
自测 3:本文的启发式为什么把"容量"放在搜索顺序的第一位?有什么量化证据?
答案要点:图 7 / 图 8 的全组合实测显示,容量对能耗的影响最大(可达 2 倍以上),行大小次之(且主要影响数据缓存),相联度最小。反证:把容量放最后的对照顺序(行 → 相联度 → 路预测 → 容量),指令缓存 18 例中 11 例没找到最优、数据缓存 18 例中 7 例没找到最优,次优配置最多多耗 7% 能量。
自测 4:mpeg2 和 pjpeg 为什么没选到全局最优?这暴露了贪心策略什么弱点?
答案要点:mpeg2 在 1 路 16 B 下,容量从 4 KB 增到 8 KB 失效率只降约 1.4 倍,省下的片外能耗不足以抵消 8 KB 的额外能耗,于是启发式停在 4 KB;但如果相联度增到 2 路,8 KB 的失效率会降约 5 倍,此时 8 KB 才划算。问题在于调容量时无法预知后面调相联度的效果。这暴露了贪心策略的经典弱点:参数间存在交互作用(interaction),逐个独立贪心会漏掉跨参数的协同最优。损失分别是 5% 和 12%。
自测 5:调优器本身的开销有多大?为什么论文强调" flush 的代价是调优器的 48 万倍"这个对比?
答案要点:调优器约 4,000 门 / 0.039 mm²(面积增加略超 3%),一次完整搜索 164 个周期,200 MHz 下功耗 2.69 mW,平均搜索能耗 11.9 nJ;而各基准访存总能耗为 0.16 mJ ~ 3.03 J(平均 2.34 J),所以调优器开销可忽略。反过来,若按”从大到小”搜索容量,写回脏数据的平均额外能耗是 5.38 mJ,与 11.9 nJ 相差约 48 万倍。这个对比说明:搜索顺序这种”实现细节”能造成 5 个数量级的代价差异,是方案能否落地的关键,而不是锦上添花。