Skip to content

沿访存依赖路线,把最近写转发、违例恢复与Store Set预测接成一个可逐步核对的窗口。你要交出的不是“乱序执行更快”这句话,而是哪次load读了谁、错误传播到哪里、恢复后哪些身份还有效,以及哪些值最终通过了退休边界。

下载完整Python标准库参考器和全部结果与事件日志。需要Python 3.10或更高版本;运行 python foundation-memory-dependence-check.py,再运行 python -O foundation-memory-dependence-check.py,两份标准输出应完全一致。程序自身不读写外部数据文件,若需保存结果,可由运行者重定向标准输出。下载的JSON是完整运行产物,不是为了页面另写的一套预期值。

一、先把输入和动作的边界写清 ​

程序是有限的 Op 列表,种类为L或S。PC是非负、四字节对齐的整数,同一个PC可以多次出现,但不能一会儿表示load、一会儿表示store。内存地址是任意整数槽名,缺省值0;它与PC的对齐规则不同,不模拟字节访问。值用数学整数,不截断为32位。布尔值不能当PC、槽名、数值、引用编号或延迟使用。

地址或store数据可以是常量,也可以是 Ref(j,a,b),表示更老load j的结果乘a再加b。引用必须指向确实存在且更老的load,不能引用年轻项、自己或store。load没有数据操作数,store必须有。每个字段的驱动释放延迟是正整数。

提交接口表,至少包含这些动作:

接口 成功时的变化 尚不能执行时
address(token) 计算该项地址;store地址首次揭晓还可能触发REPLAY 源load尚无值则WAIT
data(token) 计算store数据 源load尚无值则WAIT
finish_store(token) 地址、数据和预测前驱齐备后标DONE 字段或前驱未好则WAIT
load(token) 真实来源查询成功后记录地址、来源、结果并标DONE 地址、前驱或来源未好则WAIT
retire() 只提交DONE队首;store写M,load记录提交值 队首未好或窗口已空则WAIT
clear_history() 清历史并重建当前预测前驱 不清M,不重置已计算字段

手动调用表达的是一个合法事件次序,字段延迟只约束自动驱动器何时尝试该动作。手动样例日志的round为0,不要把它解释成这些操作都在一个硬件周期里完成。自动驱动每轮依次进行一次队首退休、地址阶段、数据阶段、store完成阶段、至多一次成功load;刚产生的load值到下一轮才能进入地址/数据阶段。

保存一个当前token,再制造后缀恢复。用旧token、另一个窗口的token,以及新造但字段相同的 Token 分别尝试动作;三者都应在改状态之前拒绝。另测重复地址动作、把load交给store数据接口、布尔编号,并比较拒绝前后的 state() 和事件列表。空程序应在0轮结束、没有事件。轮数预算耗尽应抛异常,不能当作成功的半份输出。

二、最近同址写还没数据时,不能找别人替它 ​

令当前load读槽4,M[4]=3。输入每行是 (store年龄,地址或None,数据或None);调用者应只传入这条load之前、尚未退休的store。先手算下面五项,再核对JSON中的 examples.source_queries。

较老store行 保守模式 期望结果
(0,None,17),(2,4,23) 是 WAIT,未知地址年龄0
同上 否 VALUE,来源2,值23
(0,4,17),(2,4,None) 是 WAIT,匹配数据年龄2
(0,4,17),(2,4,23) 是 VALUE,来源2,值23
(0,9,None) 是 VALUE,来源−1,值3

第三项不能返回17,也不能返回3。第五项不能因为无关store的数据没到而等待。第二项允许忽略未知地址,但它只给出这一次来源查询的许可,不独自保证整个程序安全;迟到地址仍要经过下一项的检查。

把行顺序打乱,保留年龄字段,答案应不变。再把年龄2的数据由None改成23,说明为什么解除的是匹配数据等待,而不是未知地址等待。提交最大年龄选择的证明,并给出一个把 max 错写为 min 时可见的反例。

三、先算出7,为什么最终只能提交35 ​

使用四条程序,初始M[4]=3、M[8]=0:

text
I0: store [4] := 17          PC 0x100,地址释放延迟6
I1: v1 := load [4]           PC 0x104
I2: store [8] := 2*v1+1      PC 0x108
I3: v3 := load [8]           PC 0x10c

其余延迟均为1。分别新建保守、盲猜和空历史预测窗口。每份账本保留load执行时的代次、地址、来源和数值,重放取消列表,以及每次退休后的架构M和已提交load表。

模式 第一次load执行 违例及取消 实际load执行数 退休轮数I0/I1/I2/I3
保守 I1第6轮从0得17,I3第7轮从2得35 无 2 7 / 8 / 9 / 10
盲猜 I1第1轮从M得3,I3第2轮从2得7 第6轮取消I1–I3 4 7 / 8 / 9 / 10
空历史预测 与盲猜相同 同时学习0x100与0x104 4 7 / 8 / 9 / 10

盲猜的I0地址揭晓动作发现I1来源−1<0,于是取消最老违例I1和年轻后缀。I0不取消,它保留刚得到的地址4和数据17。第7轮I0先退休,I1的新代次再从M读17;第8轮I2算出35、I3从2转发35。最终三种模式都提交load值17、35,M成为 {4:17,8:35}。

逐项解释第6轮之后I2的旧数据7、I3的旧结果7为什么都不能留下。不要只交最终M:先有错误暂定值、随后取消、最后正式提交,正是本实验要展示的差别。两种策略都到第10轮完成,因此这里只证明少做或多做了多少load,没有测出真实CPU加速比。

四、用地址传播、遮蔽与坏实现检验恢复边界 ​

做三个迁移,分别保留完整证据:

  1. 把I2地址和I3地址都改成 Ref(1),I2数据仍为 Ref(1,2,1),初始M[3]=M[17]=0。第一次年轻地址为3,恢复后必须全部重算成17。最终M是 {3:0,4:17,17:35},槽3不能留下7或35
  2. 改成晚到S0写槽4=17、早到S1写槽4=23、L2读槽4。L2从1读23;S0随后揭晓同址时,来源1并不小于store年龄0,重放次数应为0。说明中间的S1怎样遮住S0
  3. 只保留晚到S0写槽4=3和L1读槽4,初始M[4]=3。虽然读值碰巧正确,来源−1<0仍触发1次重放。协议按来源身份判定,没有实施值相等消除

再检查下载程序真正运行的两个外部错误子类。NoReplay删除违例集合,提交load值3、7,最终M[8]=7;OnlyLoad只重置违例load,提交load值17、7,M[8]仍为7。后者说明“我已重读load”不等于“我已纠正所有消费者”。这些坏实现运行时关闭参考器自带前缀校验,由另外的顺序结果比较揭示差异,不把检测器主动抛错冒充反例轨迹。

最后写退休前缀证明:若load漏读了真正最近的同址老store,那么该store地址揭晓时必会发现它;store又必须比该load先退休,错误load实例不能先越过提交边界。对依赖旧load结果的地址和数据,还要使用“取消整个年轻后缀”这一条件。只证明来源查询,不能替代这部分生命周期论证。

五、预测集合、等待前驱和数据来源各交一份 ​

保留主例冷启动学到的SSIT,再以相同初始内存运行第二次。热身后初始I1前驱为I0,第1–5轮产生5次 predicted-store 等待尝试;第6轮I0完成,I1转发17。两次load只执行两次、没有重放,仍到第10轮退休完成。I3另有5次匹配数据等待尝试;这两组尝试不能相加后冒充10个硬件停顿周期。

保持PC不变,把I0改写槽9。已学关系仍让I1等I0,但I1真实应读槽4旧值3,I3应读7;最终M为 {4:3,8:7,9:17}。指出这是正确但不必要的预测等待。再对确实训练过的历史调用清除,重跑原例:回到1次重放、4次load执行,而不是把另一个天然为空的实例当成清表测试。

还要查看 examples.live_clear:在活动窗口中先让已知地址的I1因为训练前驱0而WAIT,随后调用 clear_history()。SSIT、LFST和所有预测前驱被清空,但M与I1地址4不变。I1现在读到3,I0地址后到触发恢复并重新训练;重新执行和退休仍得到17、35。提交清前、清后、重训后三份状态。这条手动事件轨迹不报告自动驱动轮数性能。

合并实验用A=0x300、X=0x304形成集合0,B=0x308、Y=0x30c形成集合1,再训练A与Y。精确全表合并后四个PC都为0。新窗口为S0(A)写槽0=5、S1(B)写槽1=6、L2(X)读槽2,前驱为 [无,0,1]:

  • S1字段先齐,也必须等S0
  • S0完成时LFST[0]仍是1,不能清掉它
  • S1完成时才删除该LFST项
  • L2实际查询槽2,返回来源−1、值0,而不是从预测前驱1拿到6
  • 最后全部退休,M为 {0:5,1:6},已提交load为 {2:0}

公开JSON的 examples.merge包含真实load动作及全部退休,不只打印预设前驱表。解释集合号0、前驱年龄1和实际来源−1为什么回答了三个不同问题。再证明每条预测边都指向更老store,所以它不会形成等待环;同时说明空历史为什么仍可能漏掉真实冲突,独立违例恢复不能删除。

六、交付测试账本和真实费用 ​

参考器的公开检查应报告29,524次纯来源查询、7,200次随机程序运行、113,453个驱动轮、1,043次重放及19次非法动作或输入拒绝。随机程序含来自更老load的地址和数据表达式,跨三种模式、最老/最年轻两种load选择次序,并包含已有预测关系。每次退休都对照另写的顺序解释器前缀;只比最终M会漏掉错误提交后又被覆盖的写。

这些数字是固定种子和当前有限生成范围的结果,不是所有输入的穷举证明,也不是非作者独立审查结论。你自己的交付应记录范围、种子、失败轨迹和正常/优化模式结果;增加迁移时不要继续照抄旧计数。判断错误变体时还要确认关闭诊断后,运行逻辑本身确实产生错误观察。

费用表至少区分下面几层:q个较老store的数学选择可线性扫描;公开纯接口还校验m个内存项并排序未知年龄,最坏为 O(m+qlog⁡(q+1)+1)。窗口n项的地址违例扫描和后缀清除各O(n)。历史p个PC的完整集合合并最坏O(p),重建链O(n)。这些都不同于固定容量、无tag原始SSIT的实现费用。

事件日志、SSIT排序展示和每次退休的顺序前缀快照另计。默认前缀快照可占 O(n(m+n)) 空间,不能把含诊断的整个脚本说成“只有O(n)窗口”。任意精度整数也要在超过机器字时计位成本。最后说明终止条件:有限程序、有限字段延迟、总定义运算,以及持续推进队首;仅有“等待边无环”而调用者始终拒绝执行可运行的最老项,不保证完成。

回到路线时,你应能分别说明:读值正确靠最近来源规则,推测安全靠检测、完整恢复和顺序退休,预测历史只改变许可时机。这三层接好以后,才有资格在更具体的硬件模型里讨论收益。