binding_compiler_measure.py (8782B)
1 #!/usr/bin/env python3 2 """Unpublished compiler differential checks and cold-compile/runtime measurements.""" 3 import argparse,json,random,statistics,time 4 from pathlib import Path 5 from binding_substitution import ROOT 6 from abstraction_binding_model import programs,source 7 from float_multiply import Runner,sha 8 from float_adders import wrappers 9 from base_workloads import encode as nat 10 from http_headers import LEAF,TRUE,pair,tree_list,expect 11 from bundle_dag import read,Intern 12 13 14 def main(): 15 p=argparse.ArgumentParser(description=__doc__) 16 p.add_argument('tricu',type=Path);p.add_argument('--candidate',type=Path,required=True) 17 p.add_argument('--directory',type=Path,required=True);args=p.parse_args() 18 prior=json.loads((args.candidate/'report.json').read_text());assert prior['complete'] 19 assert sha(args.candidate/'candidate.arboricx')==prior['candidate_sha256'] 20 root=args.directory.resolve();root.mkdir(parents=True,exist_ok=False) 21 workspace=root/'workspace';workspace.mkdir() 22 files={'driver.tri':'bench/float-adders.tri','multiply.tri':'bench/float-multiply.tri', 23 'pipelines.tri':'bench/float-pipelines.tri','kernels.tri':'bench/float-significands.tri', 24 'layouts.tri':'bench/float-layouts.tri','native_math.tri':'lib/math/native.tri'} 25 for name,path in files.items():(workspace/name).write_bytes((ROOT/path).read_bytes()) 26 extra=''' 27 negate = K.not 28 both = K.both 29 either = K.either 30 loop x = loop x 31 copy xs = triage t (_ : t) (a b : t (t a) (copy b)) xs 32 ''' 33 extra+='\n'.join(name+' = '+source(expr) for name,expr in programs().items())+'\n' 34 extra+='bindingBranches xs = map (row : branchBound (head row) (tail row)) xs\n' 35 extra+='bindingCopies xs = map sharedBound xs\n' 36 with (workspace/'driver.tri').open('a') as f:f.write(wrappers()+extra) 37 runner=Runner(args.tricu,root,1800,memory_mib=2048) 38 report=dict(complete=False,promoted=False,harness_sha256=sha(Path(__file__)), 39 candidate_report_sha256=sha(args.candidate/'report.json'),runtime_sha256=sha(args.tricu), 40 sources={name:sha(workspace/name) for name in files},compile_samples=[],samples=[],checks=[],shapes=[]) 41 try: 42 tools={role:runner.imported(path,role) for role,path in 43 [('accepted',ROOT/'bootstrap/build-stable.arboricx'),('candidate',args.candidate/'candidate.arboricx')]} 44 # Every compilation uses a fresh output store; no warm receipt reuse. 45 rng=random.Random(8245);bundles={} 46 for repeat in range(5): 47 order=list(tools);rng.shuffle(order) 48 for role in order: 49 label=f'compile-{repeat}-{role}';bundle=root/(label+'.arboricx') 50 start=time.monotonic() 51 runner.run(label,['compile','--file',workspace/'driver.tri','--toolchain',tools[role], 52 '--tool-store',runner.store,'--store',root/(label+'-store'),'--output',bundle, 53 '--compile-timeout','240'],270) 54 row=runner.records[-1] 55 report['compile_samples'].append(dict(role=role,repeat=repeat,wall_seconds=time.monotonic()-start, 56 rts=row['rts_process'],bundle_sha256=sha(bundle))) 57 if role in bundles: 58 # Workspace and tool identities are unchanged; deterministic bundle output. 59 assert bundle.read_bytes()==bundles[role].read_bytes() 60 bundles[role]=bundle 61 intern=Intern();graphs={};pins={} 62 for role,bundle in bundles.items(): 63 _,_,nodes,exports,_=read(bundle);ids=intern.graph(nodes) 64 graphs[role]={name:ids[index] for name,index in exports.items()} 65 pins[role]=runner.imported(bundle,'import-'+role) 66 old,new=graphs['accepted'],graphs['candidate'] 67 for name in programs(): 68 changed=old[name]!=new[name];report['shapes'].append(dict(name=name,changed=changed)) 69 if name in ['singleBound','branchBound','adderBound']: 70 assert new[name]==old[name.replace('Bound','Inline')],('not expected inline',name) 71 else:assert not changed,('unexpected control change',name) 72 for bound,inline in [('ordered','inline'),('orderedMultiply','inlineMultiply')]: 73 report['shapes'].append(dict(name=bound,changed=new[bound]!=old[bound], 74 equals_accepted_inline=new[bound]==old[inline])) 75 def invoke(label,role,name,raw,wanted,measure=False): 76 path=root/(label+'.input.tree');path.write_bytes(raw) 77 actual=runner.invoke(label,pins[role],name,['--arg-tree-file',path]+(['--profile-full-result'] if measure else [])) 78 expect(actual,wanted,label) 79 if not measure:report['checks'].append(dict(label=label,role=role));return 80 points={r['profile']:r for line in (root/(label+'.log')).read_text().splitlines() 81 if line.startswith('{') for r in [json.loads(line)]} 82 a,b=points['invoke-begin-1'],points['force-end-1'] 83 return dict(wall_ns=b['wall_ns']-a['wall_ns'],allocated_bytes=b['allocated_bytes']-a['allocated_bytes'], 84 gc_ns=b['gc_elapsed_ns']-a['gc_elapsed_ns']) 85 for role in tools: 86 rows=[(a,b) for a in range(16) for b in range(16)] 87 for offset in range(0,len(rows),64): 88 chunk=rows[offset:offset+64];raw=tree_list([pair(nat(a),nat(b)) for a,b in chunk]) 89 for name,op in [('orderedFull',lambda a,b:a+b),('orderedProduct',lambda a,b:a*b)]: 90 invoke(f'oracle-{role}-{name}-{offset}',role,name,raw,tree_list([nat(op(a,b)) for a,b in chunk])) 91 for probe,wanted in [('UnknownLow',LEAF),('UnknownLowHigh',TRUE),('LowWithoutTails',TRUE), 92 ('RightZero',TRUE),('LeftZero',TRUE)]: 93 invoke(f'demand-{role}-{probe}',role,'ordered'+probe,LEAF,wanted) 94 for name,arg in [('closureBound',LEAF),('closedBranchBound',TRUE)]: 95 try:invoke(f'demand-{role}-{name}',role,name,arg,LEAF);raise AssertionError('expected timeout') 96 except RuntimeError as error: 97 if str(error)!='wall-time guard exceeded':raise 98 report['checks'].append(dict(label=name,role=role,outcome='expected-timeout')) 99 workloads={} 100 nums=[(rng.getrandbits(53),rng.getrandbits(53)) for _ in range(16)] 101 raw=tree_list([pair(nat(a),nat(b)) for a,b in nums]) 102 workloads['orderedProduct']=(raw,tree_list([nat(a*b) for a,b in nums])) 103 workloads['orderedFull']=(raw,tree_list([nat(a+b) for a,b in nums])) 104 flags=[(bool(i&1),bool(i&2)) for i in range(1024)] 105 workloads['bindingBranches']=(tree_list([pair(TRUE if a else LEAF,TRUE if b else LEAF) for a,b in flags]), 106 tree_list([pair(LEAF if b else TRUE,TRUE if b else LEAF) if a else pair(TRUE if b else LEAF,LEAF if b else TRUE) for a,b in flags])) 107 xs=list(range(128));copied=tree_list([b'\1'+nat(x) for x in xs]) 108 workloads['bindingCopies']=(nat([xs]*4),tree_list([pair(copied,copied)]*4)) 109 for repeat in range(5): 110 jobs=[(role,name) for role in tools for name in workloads];rng.shuffle(jobs) 111 for role,name in jobs: 112 raw,wanted=workloads[name] 113 measured=invoke(f'time-{repeat}-{role}-{name}',role,name,raw,wanted,True) 114 report['samples'].append(dict(role=role,name=name,repeat=repeat,**measured)) 115 report['runtime_summaries']=[] 116 for name in workloads: 117 values={role:{key:statistics.median(r[key] for r in report['samples'] if r['role']==role and r['name']==name and r['repeat']>0) 118 for key in ['wall_ns','allocated_bytes']} for role in tools} 119 report['runtime_summaries'].append(dict(name=name,medians=values, 120 candidate_over_accepted={key:values['candidate'][key]/values['accepted'][key] for key in values['accepted']})) 121 values={role:dict(wall_seconds=statistics.median(r['wall_seconds'] for r in report['compile_samples'] if r['role']==role and r['repeat']>0), 122 allocated_bytes=statistics.median(r['rts']['allocated_bytes'] for r in report['compile_samples'] if r['role']==role and r['repeat']>0)) for role in tools} 123 report['compile_summary']=dict(medians=values,candidate_over_accepted={k:values['candidate'][k]/values['accepted'][k] for k in values['accepted']}) 124 report['complete']=True;print(json.dumps(dict(compile=report['compile_summary'],runtime=report['runtime_summaries']),indent=2)) 125 finally:(root/'report.json').write_text(json.dumps(report,indent=2)+'\n') 126 127 128 if __name__=='__main__':main()