1. 线下cache
在现代计算机系统中,大多拥有多级存储结构。这是因为不同的存储器拥有不同的读写速度和成本。在我们最常见的个人电脑中,从离CPU最近到最远的顺序一般是:CPU中的寄存器、cache、内存(RAM)、外存(硬盘)。显然,他们的容量也逐渐变大,内存往往是8G或者16G,外存往往是512G或者1T。容量的大小很大一部分决定因素是其成本,意味着上述存储器的单位成本也逐渐减小(很好理解,便宜的存储器给的容量才大)。而成本又和读写速度密不可分,读写速度快的存储器性能更好,而这样的性能往往是用其他东西为代价的。例如DRAM的读写速度和功耗均差于SRAM,但是DRAM便宜,所以在电脑中DRAM的容量往往大于SRAM。
既然读写速度越快的存储器容量越少,那其存储的内容就需要有讲究,我们总是想将常用的数据存放在性能最好的存储器中,这样我们读写的平均速度就能提高。于是,在现代计算机中,cache的存储策略就成了很重要的一环(个人理解,CPU中的寄存器容量太少,对整体性能影响不大,所以不太考虑其存储策略的优化)。对于一个cache,当我们需要读取某个数据的时候,其已经存放在cache中,我们称为“命中(hit)”;如果该数据没有存放在cache中,称为“miss”。在miss的情况下,我们需要从主存储器中读取这个数据并将其放入cache,即将cache中某个数据替换掉。而线下(offline)cache指的是,我们需要读取的数据是确定的,这是一种重要的模型。现在我们接触的更多的是线上(online)cache,即需要读取的数据并不确定,是随机的,根据计算机任务的变化而变化,也是最贴近实际的模型。本文我们讨论线下cache及其最优的存储算法。
考虑下列7个数据
a
,
b
,
c
,
c
,
a
,
a
,
b
.
a,b,c,c,a,a,b.
a,b,c,c,a,a,b.假设我们有一个能存放2个数据的cache,初始状态为
{
a
,
b
}
\{a,b\}
{a,b}。当第3个数据
c
c
c来临的时候,我们遭遇一个miss,于是需要从主存储器中读出
c
c
c,并且将cache中某个数据替换成
c
c
c。这里考虑替换
a
a
a,于是cache变为
{
c
,
b
}
\{c,b\}
{c,b}。当第5个数据
a
a
a到来时,又是一次miss,这次将
c
c
c替换回
a
a
a。于是,整个过程进行了两次替换。这里举例的cache容量很小,可替换的选择很有限,但是如果是一个可以存放成千上万个数据的cache,那么每次遇到miss的时候如何替换数据,就是一个大问题了。显然,不同的替换算法,会导致不同的替换次数,替换次数越多,系统性能越差。因此,我们想找到一个线下的cache替换算法,使其替换次数总是最少的。
2. 最优线下cache规则
1960年代,Les Belady提出了一个简单的cache规则:当一串需要读取数据确定的时候,每当遇见一个miss,则考虑cache中已有的所有数据,比较这些数据在确定的需要读取的数据中的位置,选取位置最远的数据进行替换。改规则是一种最优的线下cache算法,称为Farthest-in-Future(FF)算法
这里举一个例子,假设我们的cache初始值为
{
a
,
b
,
c
,
d
,
e
,
f
}
,
\{a,b,c,d,e,f\},
{a,b,c,d,e,f},需要读取的数据为
g
,
a
,
b
,
c
,
e
,
d
,
a
,
b
,
b
,
a
,
c
,
d
,
e
,
a
,
f
,
a
,
d
,
e
,
f
,
g
,
h
.
g,a,b,c,e,d,a,b,b,a,c,d,e,a,\bm{f},a,d,e,f,g,h.
g,a,b,c,e,d,a,b,b,a,c,d,e,a,f,a,d,e,f,g,h.显然第一个数据就遭遇了miss,我们观察cache里已有的数据,发现
f
f
f是需要去读的数据中位置最远的,因此我们将cache中的
f
f
f替换为
g
g
g。
3. Farthest-in-Future(FF)算法最优性的证明
前面提到FF算法是线下cache算法中最优的,这意味着,对于确定的需要读取的序列,无论采用任何其他替换策略,总的替换次数都不会小于FF算法。FF算法的证明需要用数学归纳法,其核心思想是:对于任意一个确定的需要读取的数据,考虑一个最优替换策略
S
o
S_o
So,考虑FF算法导致的替换策略
S
F
F
S_{FF}
SFF,我们总能将
S
o
S_o
So通过某种变换方法进行变换,在这种变换方法下,每一次变换都不增加替换次数,经过一系列变换
S
o
S_o
So可以变成
S
F
F
S_{FF}
SFF,这也意味着两者的替换次数相同。
为简化文字,我们将需要读取的数据称为输入数据,每一种特定的替换策略称为一种schedule。
在证明FF算法是最优算法之前,引入reduced schedule的概念。对于一个schedule,他可以是在输入数据中的某个数据miss的时候被替换到cache中,也可以是任意时刻进行数据的替换,这意味着即使某次输入数据命中,他也可以替换cache中的某个数据。reduced schedule定义为:一个schedule,他只在miss的时候进行数据替换。
任意一个schedule
S
S
S,均可以变换成一个reduced schedule
S
′
S^{'}
S′,而不增加替换次数。证明考虑两种情况:
- S S S中某次没有miss的时候, a a a在时刻1被替换成了 b b b,而某次miss的时候,记为时刻2, b b b又被替换成了 c c c;在 S ′ S^{'} S′中, a a a在时刻2倍替换成 c c c。显然, S ′ S^{'} S′比 S S S少一次替换。
- S S S中某次没有miss的时候, a a a在时刻1被替换成了 b b b,而在时刻2,命中 b b b;在 S ′ S^{'} S′中, a a a在时刻2被替换成 b b b。显然,两者替换次数相同。
通过上述两种情况的变换,我们可以将任意schedule
S
S
S变换成一个reduced schedule
S
′
S^{'}
S′,且不增加替换次数。引入reduced schedule的意义在于简化分析,因为我们不用再考虑随机的替换的可能,只需要考虑有数据miss的情况下的替换。
显然,给定输入数据,我们总是能找到一个reduced schedule是最优的schedule(之一)。因为前面的定义,假如找到一个最优schedule不是reduced schedule,我们总是能将其变换成reduced schedule,并且不增加替换次数,那么这个reduced schedule也是一个最优schedule。下面进行FF算法最优性的证明。
给定输入数据,考虑一个reduced schedule S S S,以及FF算法给出的schedule S F F S_{FF} SFF,他们的前 j j j步相同(每一步cache中数据的组成一样,但可能顺序不一样,但是顺序不影响替换)。我们总是能将 S S S变换成一个新的schedule S ′ S^{'} S′,使得 S ′ S^{'} S′和 S F F S_{FF} SFF的前 j + 1 j+1 j+1步相同,并且 S ′ S^{'} S′的替换次数不大于 S S S。
证明:
因为 S S S和 S F F S_{FF} SFF前 j j j步相同,而 j + 1 j+1 j+1步不同,这意味着 j + 1 j+1 j+1步两个schedule进行了不同的数据替换。记第 j + 1 j+1 j+1步的输入数据为 d d d, S S S中将 f f f替换成了 d d d,而 S F F S_{FF} SFF将 e e e替换成了 d d d。显然, S S S和 S F F S_{FF} SFF在第 j j j步的时候,同时拥有 e e e和 f f f,在第 j + 1 j+1 j+1步的替换后:
- S S S的cache: { e , d , 其 他 元 素 } \{e,d,其他元素\} {e,d,其他元素}
- S F F S_{FF} SFF的cache: { d , f , 其 他 元 素 } \{d,f,其他元素\} {d,f,其他元素}
此时考虑如何构造 S ′ S^{'} S′。首先,我们令 S ′ S^{'} S′的前 j + 1 j+1 j+1步与 S F F S_{FF} SFF相同。至此,如果只考虑前 j + 1 j+1 j+1步的话, S S S、 S F F S_{FF} SFF和 S ′ S^{'} S′的替换次数是一样的。关键在于我们如何构造 S ′ S^{'} S′后面的步骤,使得其替换次数不大于 S S S。
步骤 | S S S | S F F S_{FF} SFF | S ′ S^{'} S′ |
---|---|---|---|
j j j | { e , f , 其 他 元 素 } \{e,f,其他元素\} {e,f,其他元素} | { e , f , 其 他 元 素 } \{e,f,其他元素\} {e,f,其他元素} | { e , f , 其 他 元 素 } \{e,f,其他元素\} {e,f,其他元素} |
j + 1 j+1 j+1 | { e , d , 其 他 元 素 } \{e,d,其他元素\} {e,d,其他元素} | { d , f , 其 他 元 素 } \{d,f,其他元素\} {d,f,其他元素} | { d , f , 其 他 元 素 } \{d,f,其他元素\} {d,f,其他元素} |
要使 S ′ S^{'} S′的替换次数不大于 S S S,我们可以尽量模仿 S S S的替换策略。于是从 j + 1 j+1 j+1步开始,我们尽量模仿 S S S的替换策略。但是,因为从 j + 1 j+1 j+1步开始, S S S和 S ′ S^{'} S′就有了区别,为了尽量模仿 S S S,我们需要将 S ′ S^{'} S′尽快调整至和 S S S拥有相同元素组成。如果在某一步开始, S S S和 S ′ S^{'} S′的元素组成完全一样,那么后面的步骤 S ′ S^{'} S′就能完全复制 S S S了。
如果在第 j + 1 j+1 j+1步之后的输入数据既不是 e e e也不是 f f f,并且 S S S没有替换数据 e e e,那么 S S S和 S ′ S^{'} S′拥有相同的动作。那么我们的关注点应该是,在第 j + 1 j+1 j+1步后, S S S和 S ′ S^{'} S′第一次有不同动作的时候。
这里需要注意一个重点,那就是在第 j + 1 j+1 j+1步之后,假如某次输入数据是 e e e,那么在他之前,必然有一次输入数据是 f f f。这是因为在第 j j j步的时候, S F F S_{FF} SFF替换了 e e e而不是 f f f,这意味着 f f f在后面的输入数据中,要先于 e e e出现。因此,我们在考虑在第 j + 1 j+1 j+1步后, S S S和 S ′ S^{'} S′第一次有不同动作的时候,不会出现输入数据是 e e e这一种情况。下列分析两者在第 j + 1 j+1 j+1步后第一次与不同动作的几种可能情况。
情况一(输入数据是 f f f):假如输入数据是 f f f, S S S可能把 e e e替换掉,在这种情况下, S ′ S^{'} S′命中 f f f,不需要额外动作,至此,两者的cache均为 { d , f , 其 他 元 素 } \{d,f,其他元素\} {d,f,其他元素}。另一种情况是 S S S将其他元素(非 e e e,记为 x x x)替换成 f f f,这种情况下, S ′ S^{'} S′命中 f f f。但是为了变化成和 S S S相同元素组成, S ′ S^{'} S′将 x x x替换成 e e e,此时,两者的cache均为 { e , d , f , 其 他 元 素 } \{e,d,f,其他元素\} {e,d,f,其他元素}。
情况二(输入数据既不是 e e e也不是 f f f):此时 S S S只能是替换 e e e,因为我们现在讨论的是 S S S和 S ′ S^{'} S′第一次有不同动作,假如 S S S不替换 e e e,而是其他元素,那么 S ′ S^{'} S′也可以替换其他元素,两者动作相同,不符合我们现在的讨论。当 S S S替换 e e e为新元素 g g g,为了与之相同, S ′ S^{'} S′将 f f f替换成新元素 g g g。至此,两者的cache均为 { g , d , 其 他 元 素 } \{g,d,其他元素\} {g,d,其他元素}。
上面讨论的情况1中需要注意,当 S S S将其他元素(非 e e e,记为 x x x)替换成 f f f,为了变化成和 S S S相同元素组成, S ′ S^{'} S′将 x x x替换成 e e e。这种情况下, S ′ S^{'} S′变成了一个非reduced schedule,但是,有前面的定理,我们可以将其变回reduced schedule而不增加替换次数。
在上述两种情况下,我们保证了 S S S和 S ′ S^{'} S′在经历了一次不同动作后,拥有完全相同的元素组成,这意味着,我们可以让 S ′ S^{'} S′在后续的步骤中完全模仿 S S S的步骤,使得 S ′ S^{'} S′的替换次数不大于 S S S。
综上,我们证明了这个性质。
□
\square
□
有了上述性质,我们利用数学归纳法可以很容易证明FF算法是最优的。给定输入数据,记它的一个最优reduced schedule是 S o S_o So,记FF算法下的schedule是 S F F S_{FF} SFF。 S o S_o So和 S F F S_{FF} SFF的前 j j j步相同。这里需要注意的是,我们总是能找到至少一步相同点,比如还没有数据的时候,第 0 0 0步,即cache刚填满的时候。另外一个角度看,假如cache最开始是空的,那么显然在cache被数据填满之前,两者步骤都是一样的。
因此,在保证了我们总是能找到一个最优的 S o S_o So与 S F F S_{FF} SFF在前 j j j步相同的情况下,我们利用上面的性质,将 S S S变换成一个新的schedule S ′ S^{'} S′,使得 S ′ S^{'} S′的替换次数不大于 S o S_o So,同时 S ′ S_{'} S′与 S F F S_{FF} SFF在前 j + 1 j+1 j+1步相同。如此反复,我们最终将 S S S变换成与 S F F S_{FF} SFF相同,证明到 S F F S_{FF} SFF也是一种最优的算法。
参考文献
[1] Kleinberg J., Tardos E.,Algorithm Design,2006。