<?php
/**
* ============================================================
* 《计算机体系结构:量化研究方法》第2章 存储器层次结构
* 概念 81~154 大白话讲解 + 原生PHP可运行代码
* 运行: php "D:\\1\\arch_chapter2.php"
* 说明:本章是单线程的"查表/算法模拟",原生PHP最清晰。
* 其中 #119/#120/#121 真正涉及"重叠/并行",见 arch_chapter2_swoole.php
* ============================================================
*/
function title($n,$t){ echo "\\n========== [$n] $t ==========\\n"; }
/* ———- 一个会被反复用到的"小缓存模拟器" ———-
* 用它来真实统计命中/缺失,讲很多概念都靠它。 */
class Cache {
public $sets; // 组数
public $ways; // 每组多少路(相联度)
public $blockSize; // 每块字节数
public $store = []; // [setIndex => [ tag => 最近使用序号 ]]
public $hit=0, $miss=0, $t=0;
public $policy; // LRU / FIFO / RANDOM
function __construct($sets,$ways,$blockSize=1,$policy='LRU'){
$this->sets=$sets; $this->ways=$ways; $this->blockSize=$blockSize; $this->policy=$policy;
}
// 访问一个"字节地址",返回 true=命中
function access($addr){
$this->t++;
$blockNo = intdiv($addr,$this->blockSize); // 属于第几块
$set = $blockNo % $this->sets; // 落在哪一组(index)
$tag = intdiv($blockNo,$this->sets); // 标记(tag)
if(!isset($this->store[$set])) $this->store[$set]=[];
if(isset($this->store[$set][$tag])){ // 命中
$this->hit++;
if($this->policy==='LRU') $this->store[$set][$tag]=$this->t; // 只有LRU命中才更新时间戳(FIFO不更新)
return true;
}
$this->miss++; // 缺失
if(count($this->store[$set])>=$this->ways){// 该组满了,要替换
$victim = $this->pickVictim($set);
unset($this->store[$set][$victim]);
}
$this->store[$set][$tag]=$this->t;
return false;
}
function pickVictim($set){
$entries=$this->store[$set];
if($this->policy==='RANDOM') return array_rand($entries);
// LRU和FIFO在这个简化模型里都取时间戳最小的(最久没用/最早进)
asort($entries); return array_key_first($entries);
}
function rate(){ return $this->t? round($this->hit/$this->t*100,1):0; }
}
/* 81. 存储器层次结构:越靠近CPU越快越小越贵,越远越慢越大越便宜。 */
function demo_81(){
title(81,"存储器层次结构");
$h=[["寄存器","<1ns","~1KB","最贵"],["L1缓存","~1ns","32KB",""],
["L2/L3","~10ns","几MB",""],["主存DRAM","~100ns","GB",""],
["SSD/磁盘","~10万ns","TB","最便宜"]];
foreach($h as $r) printf(" %-8s 延迟%-8s 容量%-6s %s\\n",$r[0],$r[1],$r[2],$r[3]);
echo " 核心思想:用快的当慢的'门面',靠局部性骗过用户\\n";
}
/* 82. 缓存 (Cache):把常用数据放在快的小存储里。 */
function demo_82(){
title(82,"缓存 (Cache)");
$c=new Cache(4,1);
foreach([0,1,0,2,0] as $a) $c->access($a);
echo " 访问[0,1,0,2,0] => 命中{$c->hit}次 缺失{$c->miss}次\\n";
}
/* 83. 命中/缺失:要的数据在缓存里=命中,不在=缺失(要去下一级拿)。 */
function demo_83(){
title(83,"命中 / 缺失");
$c=new Cache(4,1);
echo " 第一次访问地址5 => ".($c->access(5)?"命中":"缺失(冷启动)")."\\n";
echo " 再次访问地址5 => ".($c->access(5)?"命中":"缺失")."\\n";
}
/* 84. 命中率/缺失率:命中次数/总次数。缺失率=1-命中率。 */
function demo_84(){
title(84,"命中率 / 缺失率");
$c=new Cache(8,1);
foreach([1,2,3,1,2,3,1,2] as $a)$c->access($a);
echo " 命中率=".$c->rate()."% 缺失率=".round(100–$c->rate(),1)."%\\n";
}
/* 85. 命中时间:命中时拿到数据要多久(包括判断tag的时间)。 */
function demo_85(){
title(85,"命中时间");
echo " L1命中时间≈1个时钟周期; 命中时间越短CPU越快\\n";
}
/* 86. 缺失代价:缺失后去下一级搬一块数据回来花的时间。 */
function demo_86(){
title(86,"缺失代价 (Miss Penalty)");
echo " L1缺失要去L2/主存搬: 几十~上百周期, 远大于命中时间\\n";
}
/* 87. 块/行:缓存不是按字节存,而是一次搬一整块(利用空间局部性)。 */
function demo_87(){
title(87,"块 / 行 (Block / Line)");
$blockSize=64; $addr=130;
echo " 块大小64字节, 地址130 => 属于第".intdiv($addr,$blockSize)."块, 块内偏移".($addr%$blockSize)."\\n";
}
/* 88. 缓存映射:内存块该放进缓存哪个位置?三种规则。 */
function demo_88(){
title(88,"缓存映射");
echo " 直接映射: 每块只能去固定一个位置\\n";
echo " 全相联: 每块能去任意位置\\n";
echo " 组相联: 每块能去固定一组里的任意位置(折中)\\n";
}
/* 89. 直接映射:块号 % 缓存块数 = 唯一落点。简单但易冲突。 */
function demo_89(){
title(89,"直接映射");
$numBlocks=4;
foreach([0,4,8,1] as $b) echo " 块$b => 落点 ".($b%$numBlocks)."\\n";
echo " 注意:块0/4/8都抢落点0 => 互相踢出(冲突)\\n";
}
/* 90. 全相联:哪空放哪,查找要比对所有tag。命中率高但硬件贵。 */
function demo_90(){
title(90,"全相联");
$c=new Cache(1,4); // 1组4路 = 全相联
foreach([0,4,8,12,0] as $a)$c->access($a);
echo " 全相联(容量4)访问[0,4,8,12,0] => 命中{$c->hit} (不会因落点冲突)\\n";
}
/* 91. 组相联:先定组(直接映射),组内任意放(全相联)。最常用。 */
function demo_91(){
title(91,"组相联");
$c=new Cache(2,2); // 2组,每组2路 = 2路组相联
foreach([1,2,1,3,1] as $a)$c->access($a);
echo " 2路组相联访问[1,2,1,3,1] => 命中{$c->hit} 缺失{$c->miss}\\n";
}
/* 92. 块的查找 (Tag):地址拆成 Tag|Index|Offset 来定位和比对。 */
function demo_92(){
title(92,"块的查找 (Tag)");
$addr=0b1101_10_010; // 假设: tag=1101 index=10 offset=010
$offset=$addr & 0b111;
$index=($addr>>3)&0b11;
$tag=$addr>>5;
echo " 地址拆解: Tag=$tag Index=$index Offset=$offset\\n";
echo " 用Index找组, 用Tag比对确认是不是要的块\\n";
}
/* 93. 块的替换:组满了,踢谁走?随机/LRU/FIFO。 */
function demo_93(){
title(93,"块的替换");
echo " 策略: 随机(简单) / LRU(最久没用) / FIFO(最早进来)\\n";
}
/* 94. 随机替换:随便踢一个。硬件最省,效果意外地不差。 */
function demo_94(){
title(94,"随机替换");
$c=new Cache(1,2,1,'RANDOM');
foreach([1,2,3,1] as $a)$c->access($a);
echo " 随机替换访问[1,2,3,1] => 命中{$c->hit} 缺失{$c->miss}\\n";
}
/* 95. LRU:踢掉最久没被用的。贴合时间局部性,效果好但要记录。 */
function demo_95(){
title(95,"LRU (最近最少使用)");
$c=new Cache(1,2,1,'LRU');
foreach([1,2,1,3,2] as $a)$c->access($a); // 放3时踢掉最久没用的
echo " LRU访问[1,2,1,3,2] => 命中{$c->hit} 缺失{$c->miss}\\n";
}
/* 96. FIFO:先进先出,谁最早进来踢谁,不管最近用没用。 */
function demo_96(){
title(96,"FIFO (先进先出)");
$c=new Cache(1,2,1,'FIFO');
foreach([1,2,1,3] as $a)$c->access($a);
echo " FIFO访问[1,2,1,3] => 命中{$c->hit} 缺失{$c->miss}\\n";
}
/* 97. 写策略:写缓存时,要不要同时写下一级?两种。 */
function demo_97(){
title(97,"写策略");
echo " 写直达: 同时写缓存和主存(简单,慢)\\n";
echo " 写回: 只写缓存,标记脏,替换时才写回主存(快)\\n";
}
/* 98. 写直达:每次写都直通主存。数据总是一致,但写流量大。 */
function demo_98(){
title(98,"写直达 (Write-through)");
$cache=[]; $mem=[];
$cache[5]=99; $mem[5]=99; // 写时两边都更新
echo " 写地址5=99 => 缓存={$cache[5]}, 主存同步={$mem[5]}\\n";
}
/* 99. 写回:只改缓存+脏位,被替换时才写回主存。省带宽。 */
function demo_99(){
title(99,"写回 (Write-back)");
$cache=[5=>['val'=>99,'dirty'=>false]];
$cache[5]['val']=42; $cache[5]['dirty']=true; // 只改缓存,标脏
echo " 写地址5=42 => 缓存=42 脏位=".($cache[5]['dirty']?"1(将来才写回主存)":"0")."\\n";
}
/* 100. 写分配:写缺失时,先把那块读进缓存再写。 */
function demo_100(){
title(100,"写分配 (Write Allocate)");
echo " 写缺失 => 先把整块load进缓存, 再写 (常配写回)\\n";
}
/* 101. 非写分配:写缺失时直接写下一级,不往缓存装。 */
function demo_101(){
title(101,"非写分配 (No-write Allocate)");
echo " 写缺失 => 直接写主存,不占用缓存 (常配写直达)\\n";
}
/* 102. 写缓冲区:写的数据先丢进缓冲区,CPU不用等主存写完。 */
function demo_102(){
title(102,"写缓冲区 (Write Buffer)");
$wb=[];
$wb[]=['addr'=>5,'val'=>99]; // CPU写进缓冲就走,后台慢慢落盘
echo " 写进缓冲区即可继续执行,无需等主存. 缓冲深度=".count($wb)."\\n";
}
/* 103. 缺失的三种来源 3C:强制/容量/冲突。 */
function demo_103(){
title(103,"缺失的三种来源 (3C)");
echo " Compulsory(强制): 第一次访问,必然缺失\\n";
echo " Capacity(容量): 缓存装不下整个工作集\\n";
echo " Conflict(冲突): 组相联度不够,块互相踢\\n";
}
/* 104. 强制缺失:冷启动,任何块第一次都得从主存搬。 */
function demo_104(){
title(104,"强制缺失 (Compulsory)");
$c=new Cache(8,2);
foreach([10,20,30] as $a)$c->access($a); // 全是第一次
echo " 三个新块第一次访问 => 强制缺失{$c->miss}次(无法避免,只能靠大块/预取缓解)\\n";
}
/* 105. 容量缺失:工作集比缓存大,装不下,反复换进换出。 */
function demo_105(){
title(105,"容量缺失 (Capacity)");
$c=new Cache(1,2); // 只能装2块
foreach([1,2,3,1,2,3] as $a)$c->access($a); // 循环用3块,装不下
echo " 缓存只装2块却循环用3块 => 命中{$c->hit} 缺失{$c->miss}(容量不足)\\n";
}
/* 106. 冲突缺失:容量够,但因映射规则撞车被踢。提高相联度可解。 */
function demo_106(){
title(106,"冲突缺失 (Conflict)");
$dm=new Cache(4,1); // 直接映射
$fa=new Cache(1,4); // 全相联,同样总容量4
foreach([0,4,0,4] as $a){$dm->access($a);$fa->access($a);}
echo " 地址0和4在直接映射下抢同一落点: 直接映射缺失{$dm->miss} vs 全相联缺失{$fa->miss}\\n";
}
/* 107. AMAT:平均访问时间=命中时间+缺失率×缺失代价。最核心公式。 */
function demo_107(){
title(107,"平均存储器访问时间 (AMAT)");
$hitTime=1; $missRate=0.05; $missPenalty=100;
$amat=$hitTime+$missRate*$missPenalty;
echo " AMAT = 命中时间 + 缺失率×缺失代价 = 1 + 0.05×100 = {$amat} 周期\\n";
}
/* 108. 六种基本缓存优化:列一下。 */
function demo_108(){
title(108,"六种基本缓存优化");
foreach(["增大块大小↓强制缺失","增大缓存↓容量缺失","提高相联度↓冲突缺失",
"多级缓存↓缺失代价","读优先于写↓缺失代价","避免地址转换↓命中时间"] as $i=>$v)
echo " ".($i+1).") $v\\n";
}
/* 109. 增大块大小:一次搬更多,蹭空间局部性;但太大会增加缺失代价+污染。 */
function demo_109(){
title(109,"增大块大小");
foreach([1,4] as $bs){
$c=new Cache(4,1,$bs);
foreach(range(0,7) as $a)$c->access($a); // 顺序访问
echo " 块大小$bs: 顺序访问0~7 => 缺失{$c->miss}(大块蹭空间局部性,缺失更少)\\n";
}
}
/* 110. 增大缓存容量:能装下更多工作集,容量缺失下降;但更慢更耗电。 */
function demo_110(){
title(110,"增大缓存容量");
foreach([2,4] as $ways){
$c=new Cache(1,$ways);
foreach([1,2,3,4,1,2,3,4] as $a)$c->access($a);
echo " 容量{$ways}块: 循环用4块 => 命中{$c->hit}(容量够了就少踢)\\n";
}
}
/* 111. 提高相联度:每组更多路,减少冲突缺失;但命中时间变长。 */
function demo_111(){
title(111,"提高相联度");
foreach([1,2,4] as $ways){
$c=new Cache(4/$ways<1?1:intdiv(4,$ways),$ways);
foreach([0,4,0,4,0,4] as $a)$c->access($a); // 块0和块4
echo " {$ways}路: 命中{$c->hit} 缺失{$c->miss}\\n";
}
echo " (1路:0和4抢同一落点反复踢; 2路起两块都装得下=>大量命中)\\n";
}
/* 112. 多级缓存:L1快但小,L2/L3大但慢,层层兜底降低缺失代价。 */
function demo_112(){
title(112,"多级缓存");
$h1=1;$mr1=0.05;$h2=10;$mr2=0.20;$mp2=100;
$amat=$h1+$mr1*($h2+$mr2*$mp2);
echo " AMAT = H1 + MR1×(H2 + MR2×主存代价)\\n";
echo " = 1 + 0.05×(10 + 0.20×100) = {$amat} 周期\\n";
}
/* 113. 读缺失优先于写:写缓冲里的写让路,先服务读缺失,减少停顿。 */
function demo_113(){
title(113,"读缺失优先于写");
echo " 读缺失发生时, 让写缓冲的写排后面, 先满足读 => CPU少等\\n";
}
/* 114. 避免地址转换:缓存用虚拟地址索引,和TLB并行,缩短命中时间。 */
function demo_114(){
title(114,"避免地址转换 (虚拟索引)");
echo " 用虚拟地址直接索引缓存, 同时查TLB => 省掉串行翻译时间\\n";
}
/* 115. 十大高级缓存优化:列一下。 */
function demo_115(){
title(115,"十大高级缓存优化");
$list=["小而简单L1","路预测","流水化缓存","非阻塞缓存","多体缓存",
"关键字优先","合并写缓冲","编译器优化","硬件预取","编译器预取"];
foreach($list as $i=>$v) echo " ".($i+1).") $v\\n";
}
/* 116. 小而简单的一级缓存:L1做小做简单,命中时间短,跟得上时钟。 */
function demo_116(){
title(116,"小而简单的一级缓存");
echo " L1越小越简单 => 访问越快, 能配高主频; 容量靠L2/L3补\\n";
}
/* 117. 路预测:组相联缓存先猜命中哪一路,猜对就快如直接映射。 */
function demo_117(){
title(117,"路预测 (Way Prediction)");
$predict=['set3'=>'way0']; $actual='way0';
echo " 预测组3命中way0, 实际$actual => ".($predict['set3']==$actual?"猜对,快速返回":"猜错,多花一拍")."\\n";
}
/* 118. 流水化缓存访问:把缓存访问拆成多拍流水,提高吞吐(但命中延迟变多拍)。 */
function demo_118(){
title(118,"流水化缓存访问");
echo " 缓存访问分阶段流水(寻址->比tag->送数据) => 吞吐↑, 可跑更高频\\n";
}
/* 119. 非阻塞缓存:缺失时CPU不停,继续执行后面不相关的指令。
* ★真正涉及"重叠",见 arch_chapter2_swoole.php 用协程真演示 */
function demo_119(){
title(119,"非阻塞缓存 (Non-blocking)");
echo " 一次缺失正在搬数据时, CPU继续跑别的指令 => 重叠掉缺失代价\\n";
echo " (真重叠效果见 Swoole 版 demo)\\n";
}
/* 120. 命中下缺失:缺失还没回来,后面的命中照样先返回。 */
function demo_120(){
title(120,"命中下缺失 (Hit under Miss)");
echo " 缺失A处理中, 后续命中B/C不必等A, 直接返回 => 提高并行度\\n";
}
/* 121. 多体缓存:缓存分成几个独立bank,不同地址可同时访问。
* ★真正涉及"并行",见 Swoole 版 */
function demo_121(){
title(121,"多体缓存 (Banked Cache)");
$banks=4;
foreach([0,1,2,3] as $a) echo " 地址$a => bank".($a%$banks)."(不同bank可同时访问)\\n";
}
/* 122. 关键字优先/提前重启:先把CPU最急要的那个字送来,不等整块到齐。 */
function demo_122(){
title(122,"关键字优先 / 提前重启");
$block=[0,1,2,3]; $want=2;
echo " 要块里第{$want}个字 => 先送第{$want}个让CPU继续, 其余字随后补 => 少等\\n";
}
/* 123. 合并写缓冲:相邻地址的写在缓冲里合并成一次,减少写次数。 */
function demo_123(){
title(123,"合并写缓冲 (Merging Write Buffer)");
$wb=[];
foreach([['a'=>100,'v'=>1],['a'=>101,'v'=>2],['a'=>102,'v'=>3]] as $w){
$merged=false;
foreach($wb as &$e) if($e['a']+count($e['vals'])==$w['a']){$e['vals'][]=$w['v'];$merged=true;break;}
unset($e);
if(!$merged) $wb[]=['a'=>$w['a'],'vals'=>[$w['v']]];
}
echo " 3次相邻写合并成 ".count($wb)." 个缓冲项(一次写出去)\\n";
}
/* 124. 编译器优化:不改硬件,靠编译器调整代码访问顺序提高命中。 */
function demo_124(){
title(124,"编译器优化");
echo " 手段: 循环交换 / 分块 / 数组合并 / 循环融合 => 改善局部性\\n";
}
/* 125. 循环交换:换内外循环顺序,让访问顺着内存排列走(行优先)。 */
function demo_125(){
title(125,"循环交换 (Loop Interchange)");
$N=8; $bs=4; // 行优先存储, 块4字节
// 坏:按列遍历(跳着访问) vs 好:按行遍历(顺序访问)
$bad=new Cache($N,1,$bs); $good=new Cache($N,1,$bs);
for($j=0;$j<$N;$j++)for($i=0;$i<$N;$i++)$bad->access($i*$N+$j); // 列优先
for($i=0;$i<$N;$i++)for($j=0;$j<$N;$j++)$good->access($i*$N+$j); // 行优先
echo " 按列遍历缺失{$bad->miss} vs 按行遍历缺失{$good->miss} (顺着内存走更省)\\n";
}
/* 126. 分块:把大矩阵切成小块,让小块整个待在缓存里被反复复用。
* 用矩阵乘 C=A×B 演示:B会被反复访问,分块后小块留在缓存里。 */
function demo_126(){
title(126,"分块 (Blocking)");
$N=12; $B=4;
// 共享一个装不下整个矩阵、但装得下小块的缓存
$mk=fn()=>new Cache(16,2,1); // 容量32块,远小于矩阵12*12
// 不分块: 标准 i-j-k 三重循环
$no=$mk();
for($i=0;$i<$N;$i++)for($j=0;$j<$N;$j++)for($k=0;$k<$N;$k++){
$no->access($i*$N+$k); // A[i][k]
$no->access($N*$N+$k*$N+$j); // B[k][j]
}
// 分块: 先按 B×B 小块切, 小块内复用
$bl=$mk();
for($ii=0;$ii<$N;$ii+=$B)for($jj=0;$jj<$N;$jj+=$B)for($kk=0;$kk<$N;$kk+=$B)
for($i=$ii;$i<$ii+$B;$i++)for($j=$jj;$j<$jj+$B;$j++)for($k=$kk;$k<$kk+$B;$k++){
$bl->access($i*$N+$k);
$bl->access($N*$N+$k*$N+$j);
}
echo " 矩阵乘12×12: 不分块缺失{$no->miss} vs 分块缺失{$bl->miss}(小块留缓存里反复用)\\n";
}
/* 127. 硬件预取:硬件发现你顺序访问,提前把下一块拉来。
* 重点:预取不减少"搬运次数",而是把搬运提前、让需求访问命中(不停顿)。 */
function demo_127(){
title(127,"硬件预取");
$bs=4; $nBlocks=8; // 顺序访问8个块
// 不预取:每个新块第一次都是会让CPU停顿的"需求缺失"
$stallA=0; $present=[];
for($b=0;$b<$nBlocks;$b++){ if(!isset($present[$b])){$stallA++;$present[$b]=true;} }
// 预取:访问块b时,顺手把b+1提前搬来 => 下一次需求访问直接命中,不停顿
$stallB=0; $present=[];
for($b=0;$b<$nBlocks;$b++){
if(!isset($present[$b])) $stallB++; // 这次需求缺失(停顿)
$present[$b]=true; $present[$b+1]=true; // 预取下一块(后台搬,不停顿)
}
echo " 顺序访问8块: 不预取停顿{$stallA}次 vs 预取只停顿{$stallB}次(其余被提前藏掉)\\n";
}
/* 128. 编译器控制的预取:编译器插入预取指令,提前把要用的数据搬来。 */
function demo_128(){
title(128,"编译器控制的预取");
echo " 编译器在循环里插 prefetch(下一次要用的数据) => 计算与搬运重叠\\n";
}
/* 129. 存储器技术:SRAM快贵当缓存, DRAM慢便宜当主存, Flash当存储。 */
function demo_129(){
title(129,"存储器技术");
echo " SRAM(缓存,快贵) | DRAM(主存,慢便宜需刷新) | Flash(存储,非易失)\\n";
}
/* 130. SRAM:6个晶体管存1位,不用刷新,快,做缓存。 */
function demo_130(){
title(130,"SRAM");
echo " 6管/位, 通电就保持, 不需刷新, 速度快, 密度低=>做缓存\\n";
}
/* 131. DRAM:1管1电容存1位,电容会漏电,必须定期刷新。 */
function demo_131(){
title(131,"DRAM");
echo " 1管1电容/位, 密度高便宜, 电容漏电=>必须周期刷新(refresh)\\n";
}
/* 132. DRAM组织(行/列):内部是二维阵列,先选行(激活)再选列。 */
function demo_132(){
title(132,"DRAM 的组织 (行/列)");
$addr=1030; $cols=256;
echo " 地址$addr => 行".intdiv($addr,$cols).", 列".($addr%$cols)."\\n";
echo " 先发行地址(打开整行到行缓冲), 再发列地址取数\\n";
}
/* 133. SDRAM:同步DRAM,跟着时钟节拍工作,可连续突发传输。 */
function demo_133(){
title(133,"SDRAM");
echo " 与时钟同步, 支持突发(burst): 给一个地址连续吐出一串数据\\n";
}
/* 134. DDR:一个时钟周期上升+下降沿都传数据,带宽翻倍,逐代提速。 */
function demo_134(){
title(134,"DDR / DDR2 / DDR3 / DDR4");
foreach(["DDR"=>2.1,"DDR2"=>4.2,"DDR3"=>8.5,"DDR4"=>17] as $k=>$v)
echo " $k 约 {$v} GB/s/通道 (双沿传输,逐代翻倍)\\n";
}
/* 135. bank/rank:DRAM分多个bank/rank并行工作,藏延迟提带宽。 */
function demo_135(){
title(135,"存储器 bank / rank");
echo " 多个bank可交错访问(一个忙时另一个干活) => 并行隐藏延迟\\n";
}
/* 136. Flash:非易失,断电不丢;但写前要擦除,有寿命(磨损均衡)。 */
function demo_136(){
title(136,"Flash 存储器");
echo " NAND Flash: 断电不丢; 写前需整块擦除; 有写入寿命=>磨损均衡\\n";
}
/* 137. 相变存储器(PCM):用材料结晶/非晶态存数据,非易失,介于DRAM和Flash。 */
function demo_137(){
title(137,"相变存储器 (PCM)");
echo " 靠材料晶态/非晶态存位, 非易失, 比Flash快/寿命长, 是新型存储\\n";
}
/* 138. ECC:纠错码。多存几位校验,能发现并纠正单比特错误。
* 下面真做一个 Hamming(7,4) 单比特纠错。 */
function demo_138(){
title(138,"存储器可靠性 (ECC)");
$data=[1,0,1,1]; // 4位数据 d1d2d3d4
// 位置: 1=p1 2=p2 3=d1 4=p3 5=d2 6=d3 7=d4
$d1=$data[0];$d2=$data[1];$d3=$data[2];$d4=$data[3];
$p1=$d1 ^ $d2 ^ $d4; // 覆盖位1,3,5,7
$p2=$d1 ^ $d3 ^ $d4; // 覆盖位2,3,6,7
$p3=$d2 ^ $d3 ^ $d4; // 覆盖位4,5,6,7
$code=[$p1,$p2,$d1,$p3,$d2,$d3,$d4]; // 7位
echo " 原数据[".implode('',$data)."] 编码为7位[".implode('',$code)."]\\n";
// 故意翻转第5位(模拟内存比特翻转)
$bad=$code; $bad[4]^=1;
echo " 传输中第5位出错 => [".implode('',$bad)."]\\n";
// 重新算校验,得到syndrome指出错在第几位
$s1=$bad[0]^$bad[2]^$bad[4]^$bad[6];
$s2=$bad[1]^$bad[2]^$bad[5]^$bad[6];
$s3=$bad[3]^$bad[4]^$bad[5]^$bad[6];
$pos=$s1*1+$s2*2+$s3*4;
echo " 校验子定位出错位置=第{$pos}位 => 翻回来即纠正(SECDED思想)\\n";
}
/* 139. 虚拟存储器:给每个程序一套独立的"假地址",硬件翻译成真地址。 */
function demo_139(){
title(139,"虚拟存储器");
echo " 每个进程看到连续的虚拟地址空间, 由硬件翻译到物理内存\\n";
echo " 好处: 隔离保护 + 内存超额分配(用磁盘当后备)\\n";
}
/* 140. 页:虚拟内存按固定大小的"页"管理(如4KB)。 */
function demo_140(){
title(140,"页 (Page)");
$pageSize=4096; $va=9000;
echo " 页大小4KB, 虚拟地址$va => 页号".intdiv($va,$pageSize).", 页内偏移".($va%$pageSize)."\\n";
}
/* 141. 段:另一种划分,按逻辑(代码段/数据段/栈)分,大小可变。 */
function demo_141(){
title(141,"段 (Segment)");
echo " 段=按逻辑划分(代码/数据/栈),大小可变; 现代多用'分页'或'段页式'\\n";
}
/* 142. 页表:记录"虚拟页号->物理页框号"的映射表,放在内存里。 */
function demo_142(){
title(142,"页表 (Page Table)");
$pageTable=[0=>5, 1=>9, 2=>3]; // 虚拟页 => 物理页框
$vpn=1;
echo " 页表[$vpn] => 物理页框".$pageTable[$vpn]."\\n";
}
/* 143. 地址转换:虚拟地址=(页号,偏移) -> 查页表得页框 -> 拼成物理地址。 */
function demo_143(){
title(143,"地址转换");
$pageSize=4096; $pageTable=[2=>7];
$va=2*4096+100;
$vpn=intdiv($va,$pageSize); $off=$va%$pageSize;
$pa=$pageTable[$vpn]*$pageSize+$off;
echo " 虚拟地址$va =>(页$vpn,偏移$off)=> 物理页框".$pageTable[$vpn]." => 物理地址$pa\\n";
}
/* 144. 快表(TLB):缓存最近用过的地址翻译,避免每次都查内存里的页表。 */
function demo_144(){
title(144,"快表 (TLB)");
$tlb=[]; $pageTable=[2=>7,3=>1];
foreach([2,2,3,2] as $vpn){
if(isset($tlb[$vpn])){ echo " 页$vpn => TLB命中(快)\\n"; }
else { $tlb[$vpn]=$pageTable[$vpn]; echo " 页$vpn => TLB缺失,查页表后填入\\n"; }
}
}
/* 145. 保护与虚拟存储器:页表项带权限位,越权访问就触发异常。 */
function demo_145(){
title(145,"保护与虚拟存储器");
$pte=['frame'=>7,'read'=>true,'write'=>false]; // 只读页
$op='write';
echo " 对只读页执行$op => ".($pte[$op]?"允许":"触发保护异常(段错误)")."\\n";
}
/* 146. 多道程序的保护:每个进程有自己的页表,互相看不到对方内存。 */
function demo_146(){
title(146,"多道程序的保护");
$procA=[0=>5]; $procB=[0=>9]; // 同样虚拟页0,映射到不同物理页框
echo " 进程A的虚拟页0=>物理".$procA[0].", 进程B的虚拟页0=>物理".$procB[0]." (隔离)\\n";
}
/* 147. 虚拟机(VM):在一台真机上跑多台"假电脑",每台以为独占硬件。 */
function demo_147(){
title(147,"虚拟机 (VM)");
echo " 一台物理机上跑多个VM, 每个VM有自己的OS, 互相隔离\\n";
}
/* 148. VMM/Hypervisor:管理这些虚拟机、分配真实硬件资源的那层软件。 */
function demo_148(){
title(148,"虚拟机监视器 (VMM / Hypervisor)");
echo " VMM夹在硬件和各VM之间, 调度CPU/内存, 拦截特权指令\\n";
}
/* 149. 虚拟机对ISA的要求:特权指令必须能被VMM拦截(可虚拟化)。 */
function demo_149(){
title(149,"虚拟机对 ISA 的要求");
echo " 要求:VM执行敏感/特权指令时能陷入(trap)给VMM处理, 否则需二进制翻译\\n";
}
/* 150. 虚拟机与虚拟存储/IO:要多一层翻译(影子页表),IO也要VMM代理。 */
function demo_150(){
title(150,"虚拟机与虚拟存储器/IO");
echo " 地址要翻两次:VM虚拟->VM物理->真实物理(影子页表/嵌套页表)\\n";
}
/* 151. ARM Cortex-A8 存储层次:典型移动端配置。 */
function demo_151(){
title(151,"ARM Cortex-A8 存储层次");
echo " L1: 指令/数据各32KB; L2: 最高1MB; 面向低功耗移动设备\\n";
}
/* 152. Intel Core i7 存储层次:桌面/服务器三级缓存。 */
function demo_152(){
title(152,"Intel Core i7 存储层次");
echo " L1:32KB(每核) L2:256KB(每核) L3:8MB(共享); 大容量高性能\\n";
}
/* 153. 存储层次与性能的交互:缺失会让流水线停顿,要整体一起看。 */
function demo_153(){
title(153,"存储层次与性能的交互");
$baseCPI=1; $missPerInstr=0.02; $penalty=100;
$realCPI=$baseCPI+$missPerInstr*$penalty;
echo " 理想CPI=1, 每指令0.02次缺失×100代价 => 实际CPI=$realCPI (缺失拖慢CPU)\\n";
}
/* 154. 推测与存储层次:乱序/推测执行会提前发起访存,可能浪费也可能预热缓存。 */
function demo_154(){
title(154,"推测与存储层次");
echo " 推测执行会提前发访存请求: 猜对=提前预热缓存; 猜错=白搬(浪费带宽)\\n";
}
/* ===== 主流程 ===== */
for($i=81;$i<=154;$i++){ $fn=sprintf("demo_%d",$i); if(function_exists($fn)) $fn(); }
echo "\\n========== 第2章 81~154 演示完毕 ==========\\n";
<?php
/**
* ============================================================
* 第2章补充:#119 非阻塞缓存 / #120 命中下缺失 / #121 多体缓存
* 这三个概念的灵魂是"重叠/并行",单线程模拟会失真,故用 Swoole 6 协程
* 真正地把"缺失搬运"和"继续执行"重叠起来。
*
* 需要 swoole 扩展(>=5/6): php "D:\\1\\arch_chapter2_swoole.php"
* 没装就只读代码——它演示的是"真并发"而非"假装并发"。
* ============================================================
*/
use Swoole\\Coroutine as Co;
use Swoole\\Coroutine\\WaitGroup;
use function Swoole\\Coroutine\\run;
if(!extension_loaded('swoole')){
fwrite(STDERR,"未安装 swoole 扩展。本文件演示真并发重叠,请在装了swoole的环境运行。\\n");
exit(1);
}
/* 模拟一次"主存搬运"耗时(秒)。协程sleep期间CPU可去干别的=真重叠。 */
const MEM_LATENCY = 0.10; // 缺失代价:100ms
const HIT_TIME = 0.01; // 命中:10ms
/* ———- #119 非阻塞缓存 ———-
* 对比:阻塞式(缺失就死等,时间累加) vs 非阻塞(缺失搬运时继续执行,重叠)。 */
function demo_119_nonblocking(){
echo "\\n========== [119] 非阻塞缓存 (Swoole真重叠) ==========\\n";
// 指令流:1次缺失 + 3次命中(命中的指令和缺失不相关)
// —- 阻塞式:缺失死等,然后逐条命中 —-
$t0=microtime(true);
Co::sleep(MEM_LATENCY); // 缺失:CPU停下死等
for($i=0;$i<3;$i++) Co::sleep(HIT_TIME);// 等完才做3条命中
$blocking=microtime(true)–$t0;
// —- 非阻塞:缺失在后台搬,CPU同时把3条命中做掉 —-
$t0=microtime(true);
$wg=new WaitGroup();
$wg->add();
go(function() use($wg){ Co::sleep(MEM_LATENCY); $wg->done(); }); // 后台搬缺失块
for($i=0;$i<3;$i++) Co::sleep(HIT_TIME);// 同时继续执行命中指令
$wg->wait(); // 等缺失块也回来
$nonblock=microtime(true)–$t0;
printf(" 阻塞式耗时 %.0fms (缺失+命中串行相加)\\n",$blocking*1000);
printf(" 非阻塞耗时 %.0fms (命中重叠进了缺失的等待里)\\n",$nonblock*1000);
printf(" => 重叠省下约 %.0fms\\n",($blocking–$nonblock)*1000);
}
/* ———- #120 命中下缺失 ———-
* 缺失A还在路上,后续命中B/C不必排队,立刻返回。 */
function demo_120_hit_under_miss(){
echo "\\n========== [120] 命中下缺失 (Hit under Miss) ==========\\n";
$t0=microtime(true);
$missDone=false;
go(function() use(&$missDone){ Co::sleep(MEM_LATENCY); $missDone=true;
printf(" [%.0fms] 缺失A的数据终于回来\\n",MEM_LATENCY*1000); });
// 缺失A挂起期间,B、C命中直接返回,不等A
foreach(['B','C'] as $x){ Co::sleep(HIT_TIME);
printf(" [%.0fms] 命中%s 立即返回(没被缺失A挡住)\\n",(microtime(true)–$t0)*1000,$x); }
while(!$missDone) Co::sleep(0.005);
echo " 结论:一次缺失没堵死流水线,后面的命中照常服务\\n";
}
/* ———- #121 多体缓存 ———-
* 缓存分4个独立bank,4个落在不同bank的访问可同时进行。 */
function demo_121_banked(){
echo "\\n========== [121] 多体缓存 (Banked, 真并行) ==========\\n";
$accesses=[0,1,2,3]; // 4个地址,分别落在bank 0/1/2/3
// —- 单体:一次只能服务一个,串行 —-
$t0=microtime(true);
foreach($accesses as $a) Co::sleep(HIT_TIME);
$single=microtime(true)–$t0;
// —- 多体:4个bank并行,各服务一个 —-
$t0=microtime(true);
$wg=new WaitGroup();
foreach($accesses as $a){ $wg->add();
go(function() use($wg,$a){ Co::sleep(HIT_TIME); $wg->done(); }); } // bank a 并行服务
$wg->wait();
$banked=microtime(true)–$t0;
printf(" 单体串行 %.0fms vs 多体(4 bank)并行 %.0fms => 吞吐≈4倍\\n",$single*1000,$banked*1000);
}
run(function(){
demo_119_nonblocking();
demo_120_hit_under_miss();
demo_121_banked();
echo "\\n========== Swoole 重叠演示完毕 ==========\\n";
});


