"""STARTER for the Frontier F19 lab. Fill in the three TODOs, then run the tests with MANUAL_IMPL=starters.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:"""TODO (F19.L1): length in cycles of the longest dependency chain. Walk ops in topological order (use _topological). An op with no deps has depth 1; otherwise depth = 1 + the largest depth among its deps. Return the largest depth (0 for an empty list). """raiseNotImplementedError("F19.L1: write critical_path")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]]]:"""TODO (F19.L2): greedy list scheduling into bundles. Repeat until every op is issued: 1. ready = ops not yet issued whose deps were ALL issued in earlier cycles. 2. Sort ready by (-heights[name], name). 3. Walk that list, placing each op in this cycle's bundle if its engine still has a free slot. 4. Append the bundle ({engine: [names]}) and advance the cycle. Raise RuntimeError if a cycle issues nothing. verify_schedule checks you. """raiseNotImplementedError("F19.L2: write list_schedule")defverify_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]:"""TODO (F19.L3): search every key together, one level per step. Keep one index per key, all starting at 1. For each of the tree's levels, replace every index i with 2*i + (1 if key > tree[i] else 0). Finish by subtracting 2**depth from each index. Results must equal search_one. """raiseNotImplementedError("F19.L3: write search_batch_lockstep")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]))