Skip to content

这组任务使用同一份完整 Python 参考程序和实际执行结果。只需 Python 3.10 或更新版本,无第三方依赖。将程序保存为 systems-nonblocking-cache-check.py,在同一目录执行下列命令。每段 Python 都能单独运行,runpy 只载入定义,不触发主测试。

主线是块、组与偏移、替换历史、未决事务、逐字寿命与步长提示。最后要回到时间模型核对哪些计数真的能推出等待时间。全部内容采用单代理不可变内存,不含存储写入、取消、一致性或处理器提交。 可从完整路线入口返回八个学习站点。

任务一:先固定输入、数据来源和完整输出 ​

sh
python -B systems-nonblocking-cache-check.py > actual.json
python -O -B systems-nonblocking-cache-check.py > optimized.json
cmp actual.json optimized.json

还应将 actual.json 与下载的结果 JSON 逐字节比较。程序检查使用显式异常,不会在 -O 下被删掉。当前公开结果包括15,552个短事件序列、72,000个随机事件、49,152次预测观察及52项拒绝;接收读取数和返回数相等。具体身份与值核对不靠这个总数相等来替代。

交出地址12的完整分解:四字节字、每行四字时,它属于块0、偏移3;在内存 A[i]=17*i+11 中真实值为62。解释为什么 complete 和 deliver 只接收身份与偏移,不接收调用者自称正确的数据值。非法对齐、负地址、域外地址、布尔值和错误配置要在状态变化前拒绝。

提交输入边界表:缓存 K 和 S 为1至256的二次幂,W为1至16,M和T为1至256;内存为1至 2**20 个无符号32位整数字,长度是K的倍数。预测器的块数为1至 2**20,E为1至1024的二次幂,阈值2或3,次数1至32,全部字节地址仍在32位范围。不要把这些软件护栏说成现实处理器规格。

任务二:同时看三种容量和乱序完成 ​

sh
python -B - <<'PY'
import runpy, json
m = runpy.run_path('systems-nonblocking-cache-check.py')
c = m['WholeLineCache'](m['memory'](), 4, 2, 2, 2, 2)
warm = c.read(0); c.complete(warm.transaction)
a = c.read(16); b = c.read(20)
before = c.snapshot(); full_target = c.read(24)
if c.snapshot() != before: raise RuntimeError('retry changed state')
z = c.read(32)
full_mshr = c.read(48)
hit = c.read(0)
returned = c.complete(z.transaction) + c.complete(a.transaction)
print(json.dumps({'same_transaction': a.transaction is b.transaction,
    'reasons': [full_target.reason, full_mshr.reason], 'hit_while_full': hit.status,
    'returns': [[r.request.address, r.value] for r in returned]}))
x = m['WholeLineCache'](m['memory'](), 4, 1, 2, 3, 2)
x.read(0); x.read(16)
print(x.read(32).reason)
PY

预期同事务身份为真,两次重试原因为 target-full、mshr-full,满表期间仍为 HIT;返回依次是 [32,147]、[16,79]、[20,96]。第二个缓存输出 ways-reserved。画出两个已占路与三个全局 MSHR 的对应图,解释为什么有第三条记录的容量仍不够。

另外保存一次已接收请求和事务,构造字段一样的 Transaction(serial,block) 复制品:它必须拒绝。再检查另一个缓存发出的同序号身份及已经完成的旧身份也拒绝。请求身份用来路由结果,事务身份用来识别在途填充,不能因数值相同而混用。

任务三:所需字已返回,整行仍未完成 ​

sh
python -B - <<'PY'
import runpy, json
m = runpy.run_path('systems-nonblocking-cache-check.py')
c = m['BeatCache'](m['memory'](), 4, 1, 1, 2, 2)
a = c.read(12); c.read(0)
rows = []
for off in m['critical_order'](4, 3):
    batch = c.deliver(a.transaction, off)
    row = {'offset': off, 'returns': [r.request.address for r in batch],
           'live': len(c.pending), 'valid': c.lines[0][0].valid}
    if off == 3:
        row['same_word'] = c.read(12).status
        row['other_block'] = c.read(16).reason
    rows.append(row)
print(json.dumps(rows))
PY

逐拍返回地址为 [12]、[0]、[]、[];未决记录数为1、1、1、0,整行有效位直到最后才为真。第一拍后的同字读取是 FORWARD,另一个新块的读取是 ways-reserved。用真实62核对两次偏移3的响应,并确认它们仍是两个不同请求。

把目标容量改成1,先只接收偏移3,待它返回以后再读尚未到达的偏移0。这次应能够 MERGE,因为等待槽已释放。对已经到达的偏移3则直接 FORWARD,不占槽。请分别给出目标槽、MSHR和路的释放时刻,不要只写“请求结束”。

任务四:拆开服务顺序、提前返回和错误寿命 ​

sh
python -B - <<'PY'
import runpy, json
m = runpy.run_path('systems-nonblocking-cache-check.py')
orders = [tuple(range(4)), m['critical_order'](4, 3)]
answer = []
for order in orders:
    c = m['BeatCache'](m['memory'](), 4, 1, 1, 2, 1)
    a = c.read(12); response_time = None
    for i, off in enumerate(order):
        returned = c.deliver(a.transaction, off)
        if returned: response_time = 20 + 2*i
    answer.append({'order': order, 'response': response_time, 'full': 26})
print(json.dumps(answer))
PY

两次所需字返回为26与20,整行都在26完成。先说明第一拍20、间隔2、排列不改变首拍延迟且无争用的假设,再写公式;不能把这些数字标成实测CPU周期。若不采用提前返回,二者都必须等到26。若后来请求偏移0,旋转顺序反而让该字比自然顺序晚两单位到达。

提交两个具体错误见证。第一,偏移3到后就删除空目标事务:下一拍偏移0找不到原身份;若又允许无身份投递,则可能污染新占位者。第二,第一拍就把整行有效位置真:读尚未收到的偏移0会拿到旧数组内容0,而正确值为11。可在独立副本中改错这两行,确认不变量检查失败,然后恢复原文件。正常交付不得包含故意改坏的实现。

任务五:给每条步长提示写清历史证据 ​

sh
python -B - <<'PY'
import runpy, json
m = runpy.run_path('systems-nonblocking-cache-check.py')
p = m['StridePredictor'](16, 4, 4, 2, 2)
rows = []
for block in (1, 3, 5, 5, 7, 2, 4, 6):
    hints = p.observe(0x100, block*16)
    h = p.table[0]
    rows.append([block, h.stride, h.confidence, list(hints)])
print(json.dumps(rows))
print('conflicting PC:', p.observe(0x110, 8*16), p.table[0])
q = m['StridePredictor'](8, 4, 4, 2, 2)
print('descending:', [q.observe(0x100, b*16) for b in (7, 5, 3)])
PY

八行提示依次为空、空、[7,9]、空、[9,11]、空、空、[8,10]。同块重复不改变计数,改变步长则重置为1。冲突PC冷启动为计数0,不能继承槽内证据。下降流最后只提示块1,越界的−1被删去,不能无符号回绕。

证明计数等于“去掉连续同块重复以后,末尾相同非零差值数量与3的较小者”。再明确未来继续等差是额外前提。计数3不是正确概率75%,也不是未来规律的证明。说明本饱和计数变体与Chen–Baer原始四状态机的区别,尤其不能把这里的转移表冠以原论文的逐条实现。

任务六:把提示、接收、完成和收益分别记账 ​

sh
python -B - <<'PY'
import runpy, json
m = runpy.run_path('systems-nonblocking-cache-check.py')
for name, stream, ways in [('regular', tuple(range(8)), 2), ('pollution', (0,2,4,4), 1)]:
    for enabled in (False, True):
        print(name, enabled, json.dumps(m['consumer_trace'](stream, enabled, 2, ways)['counts']))
c = m['WholeLineCache'](m['memory'](8), 1, 2, 2, 2, 2)
hint = m['issue_hints'](c, (2,))[0]
demand = c.read(8)
batch = c.complete(hint.transaction)
print('late prefetch:', hint.status, demand.status,
      [(r.request.origin, r.value) for r in batch])
b = m['BeatCache'](m['memory'](), 4, 1, 1, 2, 2)
t = b.read(0).transaction; b.deliver(t, 0)
forward = m['issue_hints'](b, (0,))[0]
print('partial hint:', forward.status, forward.results[0].value, len(b.pending))
PY

规则流不开预取是8次需求MISS、0次HIT、8次填充;开预取是3次MISS、5次HIT、5个预取请求、仍8次填充。污染流不开预取是3次MISS、1次HIT、3次填充;开预取变成4次MISS、0次HIT、1个预取请求、5次填充。最后一例明确输出预取MISS、需求MERGE,两次各返回45;它不是预取已经到齐的需求命中。

交付时保留以上服务假设和访问分母:需求HIT、需求MISS、需求MERGE、逐字缓冲FORWARD、预取接收状态、下层事务、字拍与返回请求各自计数。最后增加的逐字例输出 FORWARD 11 1:所需首字已到,事务仍存活;它仍不是驻留HIT。生成提示本身不产生有效数据,也不会训练自己的猜测。非法提示批次在任何发出前拒绝;合法批次中的资源重试则逐项处理。

最后提交成本账本:缓存驻留数组、逐事务接收标记、目标列表、下层内存、调用者结果与诊断副本分开算;参考线性扫描和Python列表扩容不当成硬件常数周期。普通 deliver 为 O(M+T),末拍还要计接收数组回收的 O(K);生成D个提示之后,每次接收和实际填充仍有自己的费用。

通过这些任务意味着明示只读模型中的值、身份、资源寿命和条件预测都经受了检查。完整处理器、多核一致性、真实带宽排队或普遍加速结论仍需各自的模型与实验。