tricu

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

float_layouts.py (5694B)


      1 #!/usr/bin/env python3
      2 """Binary64 bit-layout benchmarks with complete field and transport checks."""
      3 import argparse
      4 import hashlib
      5 import json
      6 import platform
      7 import random
      8 import statistics
      9 from pathlib import Path
     10 
     11 from collections_batch import ProbeRunner
     12 from base_workloads import encode as natural
     13 from http_headers import LEAF, TRUE, pair, tree_list, expect
     14 
     15 ROOT = Path(__file__).resolve().parent.parent
     16 
     17 
     18 def sha(path):
     19     return hashlib.sha256(path.read_bytes()).hexdigest()
     20 
     21 
     22 def corpus(size):
     23     # Signed zeros, subnormal edges, normal edges, infinities, and NaN payloads.
     24     special = [0, 1, (1 << 52)-1, 1 << 52, 0x3ff0000000000000,
     25                0x3ff0000000000001, 0x7fefffffffffffff, 0x7ff0000000000000,
     26                0x7ff0000000000001, 0x7ff8000000000000, 0x7fffffffffffffff]
     27     words = special + [w | (1 << 63) for w in special]
     28     rng = random.Random(640053)
     29     words += [rng.getrandbits(64) for _ in range(max(0, size-len(words)))]
     30     return words
     31 
     32 
     33 def fields(w):
     34     return pair(TRUE if w >> 63 else LEAF,
     35                 pair(natural((w >> 52) & 2047), natural(w & ((1 << 52)-1))))
     36 
     37 
     38 def representations(w):
     39     return {'field': fields(w), 'packed': natural(w),
     40             'word': pair(natural(w & 0xffffffff), natural(w >> 32)),
     41             'byte': tree_list([natural(b) for b in w.to_bytes(8, 'little')])}
     42 
     43 
     44 def main():
     45     p = argparse.ArgumentParser(description=__doc__)
     46     p.add_argument('tricu', type=Path)
     47     p.add_argument('--directory', type=Path, required=True)
     48     p.add_argument('--repeats', type=int, default=5)
     49     p.add_argument('--size', type=int, default=128)
     50     p.add_argument('--seconds', type=int, default=600)
     51     args = p.parse_args()
     52     if not 3 <= args.repeats <= 15 or not 22 <= args.size <= 4096:
     53         p.error('repeats must be 3..15 and size 22..4096')
     54     root = args.directory.resolve()
     55     root.mkdir(parents=True, exist_ok=False)
     56     workspace = root/'workspace'
     57     workspace.mkdir()
     58     sources = {'driver.tri': ROOT/'bench/float-layouts.tri',
     59                'native_math.tri': ROOT/'lib/math/native.tri'}
     60     for name, path in sources.items():
     61         (workspace/name).write_bytes(path.read_bytes())
     62     runner = ProbeRunner(args.tricu, root, args.seconds, memory_mib=2048)
     63     report = dict(complete=False, scope='fresh-process-invoke-through-full-force',
     64                   runtime_sha256=sha(args.tricu), harness_sha256=sha(Path(__file__)),
     65                   compiler_sha256=sha(ROOT/'bootstrap/build-stable.arboricx'),
     66                   platform=platform.platform(), sources={k: sha(v) for k,v in sources.items()},
     67                   samples=[], summaries=[])
     68     try:
     69         tool = runner.imported(ROOT/'bootstrap/build-stable.arboricx', 'compiler')
     70         bundle = root/'layouts.arboricx'
     71         runner.run('compile', ['compile', '--file', workspace/'driver.tri',
     72             '--toolchain', tool, '--tool-store', runner.store, '--store', runner.store,
     73             '--output', bundle, '--compile-timeout', '240'], 270)
     74         pin = runner.imported(bundle, 'layouts')
     75         report['bundle_sha256'] = sha(bundle)
     76         words = corpus(args.size)
     77         report['size'] = len(words)
     78         inputs = {}
     79         for layout in ['field', 'packed', 'word', 'byte']:
     80             inputs[layout] = root/(layout+'.tree')
     81             inputs[layout].write_bytes(tree_list([representations(w)[layout] for w in words]))
     82         expected = {'Signs': tree_list([TRUE if w >> 63 else LEAF for w in words]),
     83                     'Exponents': tree_list([natural((w >> 52) & 2047) for w in words]),
     84                     'Unpacks': tree_list([fields(w) for w in words])}
     85         jobs = [(layout+op, layout, expected[op])
     86                 for layout in inputs for op in expected]
     87         jobs += [(layout+'Fast'+op, layout, expected[op])
     88                  for layout in ['packed', 'word', 'byte'] for op in ['Exponents', 'Unpacks']]
     89         jobs.append(('constantSigns', 'field', tree_list([TRUE]*len(words))))
     90         rng = random.Random(1053)
     91         for repeat in range(args.repeats):
     92             rng.shuffle(jobs)
     93             for name, layout, wanted in jobs:
     94                 label = f'{name}-{repeat}'
     95                 actual = runner.invoke(label, pin, name,
     96                     ['--arg-tree-file', inputs[layout], '--profile-full-result'])
     97                 expect(actual, wanted, label)
     98                 log = (root/(label+'.log')).read_text()
     99                 samples = {v['profile']:v for line in log.splitlines() if line.startswith('{')
    100                            for v in [json.loads(line)]}
    101                 before, after = samples['invoke-begin-1'], samples['force-end-1']
    102                 report['samples'].append(dict(name=name, repeat=repeat, warmup=repeat==0,
    103                     wall_ns=after['wall_ns']-before['wall_ns'],
    104                     allocated_bytes=after['allocated_bytes']-before['allocated_bytes']))
    105         for name in ['fieldDemand', 'wordDemand']:
    106             expect(runner.invoke(name, pin, name, ['--arg-tree-file', inputs['field']]), TRUE, name)
    107         for name, _, _ in sorted(jobs):
    108             rows = [r for r in report['samples'] if r['name']==name and not r['warmup']]
    109             row = dict(name=name)
    110             for key in ['wall_ns', 'allocated_bytes']:
    111                 row[key] = statistics.median(r[key] for r in rows)
    112                 row[key+'_min'] = min(r[key] for r in rows)
    113                 row[key+'_max'] = max(r[key] for r in rows)
    114             report['summaries'].append(row)
    115             print(json.dumps(row), flush=True)
    116         report['complete'] = True
    117     finally:
    118         (root/'report.json').write_text(json.dumps(report, indent=2)+'\n')
    119 
    120 
    121 if __name__ == '__main__':
    122     main()