tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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()