"""Frontier F19 lab: performance engineering on a toy VLIW/SIMD machine.Python 3.13 standard library only.The toy machine issues one *bundle* per cycle. A bundle may hold up to``slots[engine]`` operations for each engine, and every operation takes onecycle: its result is usable in the next cycle, never the same one. That isenough to study the two ideas that decide every cycle count:* a dependency chain cannot finish faster than its length (critical path), and* an engine cannot do more than its slots allow (resource bound).The second half of the lab searches many keys through one implicit binarytree at the same time (a batched tree traversal) and models how its costshifts between the load engine and the vector engine."""from__future__importannotationsimportmathfromdataclassesimportdataclass,field# Toy machine used throughout F19. It is deliberately small and is not a model# of any real product or of any company's interview problem.TOY_SLOTS={"alu":4,"valu":2,"load":1,"store":1}TOY_VLEN=4# ---------------------------------------------------------------------------# Part 1: dependency graphs, lower bounds, and list scheduling# ---------------------------------------------------------------------------@dataclass(frozen=True)classOp:name:strengine:strdeps:tuple[str,...]=field(default=())def_index(ops:list[Op])->dict[str,Op]:table={}foropinops:ifop.nameintable:raiseValueError(f"duplicate op name {op.name!r}")table[op.name]=opforopinops:fordinop.deps:ifdnotintable:raiseValueError(f"{op.name!r} depends on unknown op {d!r}")returntabledef_topological(ops:list[Op])->list[Op]:table=_index(ops)order,state=[],{}defvisit(name:str)->None:mark=state.get(name)ifmark==1:raiseValueError("dependency cycle")ifmark==2:returnstate[name]=1fordintable[name].deps:visit(d)state[name]=2order.append(table[name])foropinops:visit(op.name)returnorderdefcritical_path(ops:list[Op])->int:"""Length in cycles of the longest dependency chain (every op takes 1 cycle)."""depth:dict[str,int]={}foropin_topological(ops):depth[op.name]=1+max((depth[d]fordinop.deps),default=0)returnmax(depth.values(),default=0)defresource_bound(ops:list[Op],slots:dict[str,int])->int:"""Fewest cycles any schedule needs if only slot limits mattered."""counts:dict[str,int]={}foropinops:ifop.enginenotinslots:raiseValueError(f"no slots for engine {op.engine!r}")counts[op.engine]=counts.get(op.engine,0)+1returnmax((math.ceil(n/slots[e])fore,nincounts.items()),default=0)deflower_bound(ops:list[Op],slots:dict[str,int])->int:returnmax(critical_path(ops),resource_bound(ops,slots))defheights(ops:list[Op])->dict[str,int]:"""Longest chain from each op to the end of the graph, counting the op itself."""users:dict[str,list[str]]={op.name:[]foropinops}foropinops:fordinop.deps:users[d].append(op.name)h:dict[str,int]={}foropinreversed(_topological(ops)):h[op.name]=1+max((h[u]foruinusers[op.name]),default=0)returnhdeflist_schedule(ops:list[Op],slots:dict[str,int])->list[dict[str,list[str]]]:"""Greedy list scheduling: each cycle, fill every engine's slots with ready ops. An op is ready when all its dependencies were issued in earlier cycles. Among ready ops, the one with the greatest height (longest remaining chain) goes first, ties broken by name, so the result is deterministic. """table=_index(ops)h=heights(ops)issued_at:dict[str,int]={}remaining=set(table)bundles:list[dict[str,list[str]]]=[]cycle=0whileremaining:ready=[nforninremainingifall(issued_at.get(d,cycle)<cyclefordintable[n].deps)]ready.sort(key=lambdan:(-h[n],n))used:dict[str,int]={e:0foreinslots}bundle:dict[str,list[str]]={}forninready:e=table[n].engineifused[e]<slots[e]:used[e]+=1bundle.setdefault(e,[]).append(n)issued_at[n]=cycleifnotbundle:raiseRuntimeError("no progress: check for an engine with zero slots")remaining-={nfornamesinbundle.values()forninnames}bundles.append(bundle)cycle+=1returnbundlesdefverify_schedule(ops:list[Op],slots:dict[str,int],bundles:list[dict[str,list[str]]])->None:"""Raise ValueError unless every op runs once, after its deps, within slot limits."""table=_index(ops)issued_at:dict[str,int]={}forcycle,bundleinenumerate(bundles):forengine,namesinbundle.items():iflen(names)>slots.get(engine,0):raiseValueError(f"cycle {cycle}: {len(names)} ops on {engine!r}, limit {slots.get(engine,0)}")forninnames:ifninissued_at:raiseValueError(f"{n!r} issued twice")iftable[n].engine!=engine:raiseValueError(f"{n!r} placed on {engine!r}, belongs on {table[n].engine!r}")issued_at[n]=cycleforn,opintable.items():ifnnotinissued_at:raiseValueError(f"{n!r} never issued")fordinop.deps:ifissued_at[d]>=issued_at[n]:raiseValueError(f"{n!r} in cycle {issued_at[n]} uses {d!r} from cycle {issued_at[d]}")defutilization(bundles:list[dict[str,list[str]]],slots:dict[str,int])->dict[str,float]:"""Fraction of each engine's slots that the schedule filled."""cycles=len(bundles)ifcycles==0:return{e:0.0foreinslots}return{e:sum(len(b.get(e,[]))forbinbundles)/(slots[e]*cycles)foreinslots}defindependent_chains(chains:int,length:int,engine:str="valu")->list[Op]:"""``chains`` independent chains of ``length`` dependent ops, as in an unrolled loop."""ops=[]forcinrange(chains):forkinrange(length):deps=(f"c{c}_{k-1}",)ifkelse()ops.append(Op(f"c{c}_{k}",engine,deps))returnops# ---------------------------------------------------------------------------# Part 2: batched traversal of an implicit binary search tree# ---------------------------------------------------------------------------defeytzinger(sorted_keys:list[int])->list[int|None]:"""Lay sorted keys out as a complete binary search tree in an array. Index 1 is the root; node i has children 2i and 2i + 1; index 0 is unused. len(sorted_keys) must be 2**depth - 1 so every level is full. """n=len(sorted_keys)depth=(n+1).bit_length()-1ifn==0or2**depth-1!=n:raiseValueError("need 2**depth - 1 keys")ifany(a>=bfora,binzip(sorted_keys,sorted_keys[1:])):raiseValueError("keys must be strictly increasing")tree:list[int|None]=[None]*(n+1)it=iter(sorted_keys)deffill(i:int)->None:# in-order walk visits keys in sorted orderifi<=n:fill(2*i)tree[i]=next(it)fill(2*i+1)fill(1)returntreedeftree_depth(tree:list[int|None])->int:return(len(tree)).bit_length()-1defsearch_one(tree:list[int|None],key:int)->int:"""Rank of ``key``: how many stored keys are strictly smaller. Branch-free descent: go right exactly when key > node, so the path spells the answer in binary. After ``depth`` steps, i - 2**depth is the rank. """depth=tree_depth(tree)i=1for_inrange(depth):i=2*i+(1ifkey>tree[i]else0)returni-2**depthdefsearch_batch_lockstep(tree:list[int|None],keys:list[int])->list[int]:"""Search all keys together, one tree level per step (level-synchronous). Every key takes exactly ``depth`` steps, so the whole batch moves down the tree in lockstep. That regular shape is what lets SIMD lanes each carry one key and a VLIW scheduler overlap independent keys. """depth=tree_depth(tree)idx=[1]*len(keys)for_inrange(depth):idx=[2*i+(1ifk>tree[i]else0)fori,kinzip(idx,keys)]return[i-2**depthforiinidx]defdistinct_nodes_per_level(tree:list[int|None],keys:list[int])->list[int]:"""How many different nodes the batch touches at each level."""depth=tree_depth(tree)idx=[1]*len(keys)out=[]for_inrange(depth):out.append(len(set(idx)))idx=[2*i+(1ifk>tree[i]else0)fori,kinzip(idx,keys)]returnout@dataclass(frozen=True)classTraversalCost:loads:intvalu:intload_bound:intvalu_bound:int@propertydefbound(self)->int:returnmax(self.load_bound,self.valu_bound)deftraversal_cost(batch:int,depth:int,cached_levels:int,*,vlen:int=TOY_VLEN,slots:dict[str,int]|None=None)->TraversalCost:"""Operation counts and resource bounds for a vectorized lockstep traversal. Model (stated so you can challenge it): * Keys ride in vectors of ``vlen`` lanes: ceil(batch / vlen) vectors. * Every level costs 2 vector ops per vector: a compare and an index update. * An uncached level needs each lane's node value from memory. Addresses differ per lane, so that is one scalar load per key per level. * The top ``cached_levels`` levels (2**c - 1 nodes) are loaded once, with contiguous vector loads, and kept resident. Picking a lane's node value at a cached level with 2**l candidates takes 2**l - 1 vector selects per vector (level 0 is a single broadcast value and needs none). """ifnot0<=cached_levels<=depth:raiseValueError("cached_levels must be between 0 and depth")slots=TOY_SLOTSifslotsisNoneelseslotsvectors=math.ceil(batch/vlen)preload=math.ceil((2**cached_levels-1)/vlen)loads=batch*(depth-cached_levels)+preloadselects=vectors*sum(2**level-1forlevelinrange(cached_levels))valu=vectors*2*depth+selectsreturnTraversalCost(loads,valu,math.ceil(loads/slots["load"]),math.ceil(valu/slots["valu"]))defbest_cached_levels(batch:int,depth:int,**kwargs)->tuple[int,TraversalCost]:"""Cached-level count with the smallest resource bound (ties: fewer levels)."""options=[(c,traversal_cost(batch,depth,c,**kwargs))forcinrange(depth+1)]returnmin(options,key=lambdaitem:(item[1].bound,item[0]))