diff options
| author | eunhhu <[email protected]> | 2026-10-19 14:47:10 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-19 14:47:10 +0100 |
| commit | 415bc5af13f6658ac44a0d135bb0f290191a44f6 (patch) | |
| tree | 2f14be1edb5d11bda28b3e644b8c426503e3e361 /research/experiments | |
| parent | a93891ee72bb1793eb1845d6d49369cb9f1e8806 (diff) | |
| download | pure-415bc5af13f6658ac44a0d135bb0f290191a44f6.tar.gz pure-415bc5af13f6658ac44a0d135bb0f290191a44f6.zip | |
047: "the leaf is the commit" measured against the bars set before: appends pass, sparse updates do not, as said beforehand that means two units, not one: list bytes a message at 1 / 16 / 256 a commit, entries' appends today 65.5 / 15.7 / 12.4, leaves cow 1,636 / 102 / 14.5, leaves with appends in place past every kept snapshot's count (tail) 25.9 / 10.9 / 10.0; the bank's transfers today 122 / 35.6 / 30.1 (bars 244 / 71 / 60), cow 7,428 / 2,098 / 996, tail 4,826 / 1,975 / 992, cells written in place 186 / 126 / 122 but seen by kept snapshots (933,888 / 542,208 / 10,848 times); after the run, a leaf with room for changes (delta, 16 B each, the leaf written anew when full): 739 / 545 / 292 with room 8, 363 / 268 / 176 with 32, 249 / 161 / 136 with 128, every one over (today's log puts a change in about 7.5 B: varint places, zigzag wholes, one frame a commit); no kept snapshot sees a write of cow, tail or delta; rows as leaves (packet.c): 44.4 machine instructions a row to check and take 32-row column chunks against pure_list_read's 1,318 (cycles 14.2 against 372), every cell the same
Diffstat (limited to 'research/experiments')
4 files changed, 177 insertions, 7 deletions
diff --git a/research/experiments/047-leaf-commit/2026-10-19-model.jsonl b/research/experiments/047-leaf-commit/2026-10-19-model.jsonl new file mode 100644 index 0000000..d4db485 --- /dev/null +++ b/research/experiments/047-leaf-commit/2026-10-19-model.jsonl @@ -0,0 +1,30 @@ +{"workload": "entries", "messages": 20000, "batch": 1, "way": "cow", "commits": 20000, "list_bytes_per_message": 1636.1, "by_part_per_message": {"leaves": 296.0, "cells": 0.0, "inner": 1324.1, "entries": 0.0, "deltas": 0.0, "records": 144.0, "compaction": 128.3}, "all_bytes_per_message": 1892.4, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 1, "way": "tail", "commits": 20000, "list_bytes_per_message": 25.9, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 144.0, "compaction": 4.7}, "all_bytes_per_message": 158.6, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 1, "way": "inplace", "commits": 20000, "list_bytes_per_message": 25.9, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 144.0, "compaction": 4.7}, "all_bytes_per_message": 158.6, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 1, "way": "delta", "commits": 20000, "list_bytes_per_message": 25.9, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 144.0, "compaction": 11.8}, "all_bytes_per_message": 165.7, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 16, "way": "cow", "commits": 1250, "list_bytes_per_message": 102.3, "by_part_per_message": {"leaves": 18.5, "cells": 0.0, "inner": 82.8, "entries": 0.0, "deltas": 0.0, "records": 9.0, "compaction": 7.5}, "all_bytes_per_message": 117.8, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 16, "way": "tail", "commits": 1250, "list_bytes_per_message": 10.9, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 9.0, "compaction": 0.0}, "all_bytes_per_message": 18.9, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 16, "way": "inplace", "commits": 1250, "list_bytes_per_message": 10.9, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 9.0, "compaction": 0.0}, "all_bytes_per_message": 18.9, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 16, "way": "delta", "commits": 1250, "list_bytes_per_message": 10.9, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 9.0, "compaction": 0.0}, "all_bytes_per_message": 18.9, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 256, "way": "cow", "commits": 79, "list_bytes_per_message": 14.5, "by_part_per_message": {"leaves": 9.2, "cells": 0.0, "inner": 5.2, "entries": 0.0, "deltas": 0.0, "records": 0.6, "compaction": 0.0}, "all_bytes_per_message": 15.1, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 256, "way": "tail", "commits": 79, "list_bytes_per_message": 10.0, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 0.6, "compaction": 0.0}, "all_bytes_per_message": 10.5, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 256, "way": "inplace", "commits": 79, "list_bytes_per_message": 10.0, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 0.6, "compaction": 0.0}, "all_bytes_per_message": 10.5, "snapshot_violations": 0} +{"workload": "entries", "messages": 20000, "batch": 256, "way": "delta", "commits": 79, "list_bytes_per_message": 10.0, "by_part_per_message": {"leaves": 1.2, "cells": 8.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 0.6, "compaction": 0.0}, "all_bytes_per_message": 10.5, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 1, "way": "cow", "commits": 20000, "list_bytes_per_message": 7428.1, "by_part_per_message": {"leaves": 4000.0, "cells": 0.0, "inner": 3364.1, "entries": 0.0, "deltas": 0.0, "records": 192.0, "compaction": 1729.6}, "all_bytes_per_message": 9285.7, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 1, "way": "tail", "commits": 20000, "list_bytes_per_message": 4825.9, "by_part_per_message": {"leaves": 2681.2, "cells": 40.0, "inner": 2040.0, "entries": 0.6, "deltas": 0.0, "records": 192.0, "compaction": 1129.2}, "all_bytes_per_message": 6083.1, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 1, "way": "inplace", "commits": 20000, "list_bytes_per_message": 185.9, "by_part_per_message": {"leaves": 1.2, "cells": 120.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 192.0, "compaction": 55.1}, "all_bytes_per_message": 369.0, "snapshot_violations": 933888} +{"workload": "bank", "messages": 20000, "batch": 1, "way": "delta", "commits": 20000, "list_bytes_per_message": 738.9, "by_part_per_message": {"leaves": 331.5, "cells": 40.0, "inner": 217.2, "entries": 0.6, "deltas": 85.6, "records": 192.0, "compaction": 146.4}, "all_bytes_per_message": 1013.3, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 16, "way": "cow", "commits": 1250, "list_bytes_per_message": 2098.0, "by_part_per_message": {"leaves": 1581.0, "cells": 0.0, "inner": 512.9, "entries": 0.0, "deltas": 0.0, "records": 12.0, "compaction": 443.9}, "all_bytes_per_message": 2549.8, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 16, "way": "tail", "commits": 1250, "list_bytes_per_message": 1974.6, "by_part_per_message": {"leaves": 1499.8, "cells": 40.0, "inner": 430.2, "entries": 0.6, "deltas": 0.0, "records": 12.0, "compaction": 397.6}, "all_bytes_per_message": 2380.3, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 16, "way": "inplace", "commits": 1250, "list_bytes_per_message": 125.9, "by_part_per_message": {"leaves": 1.2, "cells": 120.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 12.0, "compaction": 0.0}, "all_bytes_per_message": 133.9, "snapshot_violations": 542208} +{"workload": "bank", "messages": 20000, "batch": 16, "way": "delta", "commits": 1250, "list_bytes_per_message": 544.7, "by_part_per_message": {"leaves": 298.2, "cells": 40.0, "inner": 140.6, "entries": 0.6, "deltas": 61.2, "records": 12.0, "compaction": 115.8}, "all_bytes_per_message": 668.5, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 256, "way": "cow", "commits": 79, "list_bytes_per_message": 996.0, "by_part_per_message": {"leaves": 958.3, "cells": 0.0, "inner": 37.5, "entries": 0.0, "deltas": 0.0, "records": 0.8, "compaction": 220.8}, "all_bytes_per_message": 1217.3, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 256, "way": "tail", "commits": 79, "list_bytes_per_message": 991.5, "by_part_per_message": {"leaves": 918.3, "cells": 40.0, "inner": 32.3, "entries": 0.6, "deltas": 0.0, "records": 0.8, "compaction": 220.8}, "all_bytes_per_message": 1212.7, "snapshot_violations": 0} +{"workload": "bank", "messages": 20000, "batch": 256, "way": "inplace", "commits": 79, "list_bytes_per_message": 122.2, "by_part_per_message": {"leaves": 1.2, "cells": 120.0, "inner": 0.0, "entries": 0.6, "deltas": 0.0, "records": 0.8, "compaction": 0.0}, "all_bytes_per_message": 122.7, "snapshot_violations": 10848} +{"workload": "bank", "messages": 20000, "batch": 256, "way": "delta", "commits": 79, "list_bytes_per_message": 292.3, "by_part_per_message": {"leaves": 169.0, "cells": 40.0, "inner": 29.0, "entries": 0.6, "deltas": 53.3, "records": 0.8, "compaction": 0.0}, "all_bytes_per_message": 292.8, "snapshot_violations": 0} +{"room": 32, "workload": "bank", "messages": 20000, "batch": 1, "way": "delta", "commits": 20000, "list_bytes_per_message": 362.9, "by_part_per_message": {"leaves": 114.1, "cells": 40.0, "inner": 50.8, "entries": 0.6, "deltas": 93.4, "records": 192.0, "compaction": 103.2}, "all_bytes_per_message": 594.2, "snapshot_violations": 0} +{"room": 32, "workload": "bank", "messages": 20000, "batch": 16, "way": "delta", "commits": 1250, "list_bytes_per_message": 268.1, "by_part_per_message": {"leaves": 114.1, "cells": 40.0, "inner": 42.9, "entries": 0.6, "deltas": 66.4, "records": 12.0, "compaction": 0.0}, "all_bytes_per_message": 276.1, "snapshot_violations": 0} +{"room": 32, "workload": "bank", "messages": 20000, "batch": 256, "way": "delta", "commits": 79, "list_bytes_per_message": 175.6, "by_part_per_message": {"leaves": 63.3, "cells": 40.0, "inner": 13.6, "entries": 0.6, "deltas": 57.8, "records": 0.8, "compaction": 0.0}, "all_bytes_per_message": 176.1, "snapshot_violations": 0} +{"room": 128, "workload": "bank", "messages": 20000, "batch": 1, "way": "delta", "commits": 20000, "list_bytes_per_message": 248.6, "by_part_per_message": {"leaves": 43.2, "cells": 40.0, "inner": 5.2, "entries": 0.6, "deltas": 95.5, "records": 192.0, "compaction": 0.0}, "all_bytes_per_message": 376.6, "snapshot_violations": 0} +{"room": 128, "workload": "bank", "messages": 20000, "batch": 16, "way": "delta", "commits": 1250, "list_bytes_per_message": 160.8, "by_part_per_message": {"leaves": 43.2, "cells": 40.0, "inner": 5.1, "entries": 0.6, "deltas": 67.8, "records": 12.0, "compaction": 0.0}, "all_bytes_per_message": 168.8, "snapshot_violations": 0} +{"room": 128, "workload": "bank", "messages": 20000, "batch": 256, "way": "delta", "commits": 79, "list_bytes_per_message": 135.8, "by_part_per_message": {"leaves": 33.4, "cells": 40.0, "inner": 2.5, "entries": 0.6, "deltas": 59.0, "records": 0.8, "compaction": 0.0}, "all_bytes_per_message": 136.3, "snapshot_violations": 0} diff --git a/research/experiments/047-leaf-commit/2026-10-19-packet.jsonl b/research/experiments/047-leaf-commit/2026-10-19-packet.jsonl new file mode 100644 index 0000000..6cf5bf2 --- /dev/null +++ b/research/experiments/047-leaf-commit/2026-10-19-packet.jsonl @@ -0,0 +1 @@ +{"rows":2787,"fields":6,"chunks":88,"rounds":50,"today_instructions_per_row":1318.4,"today_cycles_per_row":372.4,"leaves_instructions_per_row":44.4,"leaves_cycles_per_row":14.2,"answers":"every cell the same both ways"} diff --git a/research/experiments/047-leaf-commit/model.py b/research/experiments/047-leaf-commit/model.py index a441166..9d9e890 100644 --- a/research/experiments/047-leaf-commit/model.py +++ b/research/experiments/047-leaf-commit/model.py @@ -30,6 +30,10 @@ The bars, set before running (today's list bytes, the log and images, by groupin - appends: the leaf commit's list bytes a message at most today's (entries: 65.5 / 15.7 / 12.4); - sparse updates: at most twice today's (bank: 2 x 122 / 35.6 / 30.1 = 244 / 71.2 / 60.2); - snapshot: no in-place write a kept snapshot can see (cow and tail; inplace is there to break it). +After the run (not in the bars above, so not held to them): delta, the leaf with room for 8 changes after its cells +(16 B each: its place, field and cell), appended in place, a full room writing the leaf anew with them folded in. One +object still: the log's records live in the leaf they change. Its readers' price (a read looks at the room) is not +counted here. What it means, said before: if appends pass and updates do not, "one unit" fails as stated: the leaf is the unit for appends and bulk, and a sparse change needs a delta form, which is what the log is; the honest design is then two units (or the log kept for updates alone), not one. @@ -42,6 +46,9 @@ import sys HEADER, INNER, FANOUT, RECORD, PER_LIST, SLOTS, SLACK = 40, 680, 32, 128, 16, 8192, 1 << 20 ENTRY = 20 # a child entry written into a parent in place: its pointer (8), count before (4) and key (8) KEEP_EVERY, KEPT = 64, 16 +DELTAS, DELTA = 8, 16 # delta: a leaf's room for changes after its cells (u32 place, u8 field, pad, the 8-byte cell) +import os # noqa: E402 +DELTAS = int(os.environ.get('MODEL_DELTAS', DELTAS)) class List: @@ -51,6 +58,8 @@ class List: self.name, self.cell, self.items = name, cell, items self.leaf = HEADER + FANOUT * cell self.version = {} # node (level, index) -> the commit that wrote it + self.deltas = {} # delta: a leaf's changes in its room since it was last written whole + self.written = {} # delta: how often a leaf was written whole (its version: a new one is another object) def leaves(self): return max(1, -(-self.items // FANOUT)) @@ -71,7 +80,7 @@ class List: return out def live(self): - n, total, level = self.leaves(), self.leaves() * self.leaf, 0 + n, total, level = self.leaves(), self.leaves() * (self.leaf + DELTAS * DELTA * (self.way == 'delta')), 0 while n > 1: n = -(-n // FANOUT) total += n * INNER @@ -97,7 +106,11 @@ def workload(name, messages, seed=47): def run(name, messages, batch, way): lists, changes = workload(name, messages) - written = {'leaves': 0, 'cells': 0, 'inner': 0, 'entries': 0, 'records': 0, 'compaction': 0} + for l in lists.values(): + l.way = way + if way == 'delta': + l.leaf += DELTAS * DELTA # the room is part of the leaf + written = {'leaves': 0, 'cells': 0, 'inner': 0, 'entries': 0, 'deltas': 0, 'records': 0, 'compaction': 0} end, live0 = SLOTS, sum(l.live() for l in lists.values()) end += live0 # what the setup loaded (not counted: baseline.py takes it away too) kept = [] # snapshots kept: {list name: count it reads} @@ -106,6 +119,8 @@ def run(name, messages, batch, way): group = changes[start:start + batch] touched = {n: set() for n in lists} # leaves to write anew tail_cells = {n: 0 for n in lists} + delta_writes = {n: 0 for n in lists} + delta_leaves = {n: set() for n in lists} for message in group: for list_name, op, position in message: l = lists[list_name] @@ -113,7 +128,7 @@ def run(name, messages, batch, way): at = l.items l.items += 1 leaf = at // FANOUT - if way in ('tail', 'inplace'): + if way in ('tail', 'inplace', 'delta'): for snapshot in kept: # a kept snapshot never reads at or past its count if at < snapshot.get(list_name, 0): violations += 1 @@ -129,6 +144,21 @@ def run(name, messages, batch, way): written['inner'] += HEADER else: touched[list_name].add(leaf) + elif way == 'delta': # the change appended into its leaf's room, in place; a full room: the leaf anew + leaf = position // FANOUT + n, version = l.deltas.get(leaf, 0), l.written.get(leaf, 0) + for snapshot in kept: # a kept snapshot reads no more of its leaf's room than its commit said + seen = snapshot.get((list_name, leaf)) + if seen and seen[0] == version and n < seen[1]: + violations += 1 + if n < DELTAS: + l.deltas[leaf] = n + 1 + delta_writes[list_name] += 1 + delta_leaves[list_name].add(leaf) + else: + l.deltas[leaf] = 0 + l.written[leaf] = version + 1 + touched[list_name].add(leaf) elif way == 'inplace': # the cell written where it is: what a kept snapshot (and a crash) would see for snapshot in kept: if position < snapshot.get(list_name, 0): @@ -148,19 +178,24 @@ def run(name, messages, batch, way): for n, cells in tail_cells.items(): written['cells'] += cells * lists[n].cell size += cells * lists[n].cell - changed = sum(1 for n in lists if touched[n] or tail_cells[n]) + for n in lists: # delta: each change's 16 bytes, and each leaf with new ones its 16 in the commit record + written['deltas'] += delta_writes[n] * DELTA + PER_LIST * len(delta_leaves[n]) + size += delta_writes[n] * DELTA + PER_LIST * len(delta_leaves[n]) + changed = sum(1 for n in lists if touched[n] or tail_cells[n] or delta_writes[n]) written['records'] += RECORD + PER_LIST * changed end += size + RECORD + PER_LIST * changed commits += 1 if commits % KEEP_EVERY == 0: - kept.append({n: l.items for n, l in lists.items()}) + snapshot = {n: l.items for n, l in lists.items()} + snapshot.update({(n, leaf): (l.written.get(leaf, 0), c) for n, l in lists.items() for leaf, c in l.deltas.items()}) + kept.append(snapshot) kept = kept[-KEPT:] live = sum(l.live() for l in lists.values()) if end - SLOTS > 4 * live + SLACK: written['compaction'] += live end = SLOTS + live total = sum(written.values()) - lists_bytes = written['leaves'] + written['cells'] + written['inner'] + written['entries'] + lists_bytes = written['leaves'] + written['cells'] + written['inner'] + written['entries'] + written['deltas'] return {'workload': name, 'messages': messages, 'batch': batch, 'way': way, 'commits': commits, 'list_bytes_per_message': round((lists_bytes + written['records'] - RECORD * commits) / messages, 1), 'by_part_per_message': {k: round(v / messages, 1) for k, v in written.items()}, @@ -170,7 +205,7 @@ def run(name, messages, batch, way): def main(): name, messages = sys.argv[1], int(sys.argv[2]) for batch in (1, 16, 256): - for way in ('cow', 'tail', 'inplace'): + for way in ('cow', 'tail', 'inplace', 'delta'): print(json.dumps(run(name, messages, batch, way)), flush=True) diff --git a/research/experiments/047-leaf-commit/packet.c b/research/experiments/047-leaf-commit/packet.c new file mode 100644 index 0000000..b079a4a --- /dev/null +++ b/research/experiments/047-leaf-commit/packet.c @@ -0,0 +1,104 @@ +/* Research 047: a message's rows as leaves (feedback 009: packet bytes = memory leaf), against today's reading. + * The rows of UAX #14's load message (6 fields of 8 bytes, 2,787 rows: tests/specs/linebreak/ranges.bin): + * today pure_list_read into an event's list, as message() does (the list made a tree, each row decoded and + * inserted) + * leaves the same rows sent as leaves: chunks of 32 rows, each field's 32 cells together (the memory leaf's + * columns, core/module-list.c), checked as decode_cell checks them (each number finite, each bool 0 or 1) + * and taken where they are: a chunk's address kept, nothing copied + * Machine instructions and cycles a row (perf_event_open, user mode), ROUNDS passes each. + * + * cc -O2 -std=c17 -I../../../core packet.c ../../../build/libpure.a -lm -pthread -o packet + * ./packet ranges.bin ROUNDS one JSON line */ +#define _GNU_SOURCE +#include "module-list.h" +#include <linux/perf_event.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/syscall.h> +#include <unistd.h> + +enum { FIELDS = 6, RUN = 32 }; +static const uint8_t kinds[FIELDS] = {PURE_CELL_NUMBER, PURE_CELL_NUMBER, PURE_CELL_BOOL, PURE_CELL_BOOL, PURE_CELL_BOOL, PURE_CELL_BOOL}; + +static int counter(uint64_t config) { + struct perf_event_attr a; + memset(&a, 0, sizeof(a)); + a.size = sizeof(a), a.type = PERF_TYPE_HARDWARE, a.config = config, a.disabled = 1, a.exclude_kernel = 1, a.exclude_hv = 1; + return (int)syscall(SYS_perf_event_open, &a, 0, -1, -1, 0); +} +static uint64_t read_counter(int fd) { + uint64_t v = 0; + if (read(fd, &v, sizeof(v)) != sizeof(v)) return 0; + return v; +} + +/* A leaf as memory keeps one: a 40-byte header (unused here but laid out), then each field's 32 cells. */ +typedef struct { unsigned char header[40]; double cells[FIELDS][RUN]; } Leaf; + +/* The leaves taken: each checked, then its address kept. */ +static __attribute__((noinline)) size_t take_leaves(const Leaf *leaves, size_t chunks, size_t rows, const Leaf **kept) { + for (size_t c = 0; c < chunks; ++c) { + const Leaf *leaf = &leaves[c]; + size_t n = c + 1 < chunks ? RUN : rows - c * RUN; + for (unsigned f = 0; f < FIELDS; ++f) { + const double *column = leaf->cells[f]; + if (kinds[f] == PURE_CELL_BOOL) { + for (size_t i = 0; i < n; ++i) if (!(column[i] == 0 || column[i] == 1)) return SIZE_MAX; + } else + for (size_t i = 0; i < n; ++i) if (!isfinite(column[i])) return SIZE_MAX; + } + kept[c] = leaf; + } + return rows; +} + +int main(int argc, char **argv) { + if (argc != 3) { fprintf(stderr, "usage: %s ranges.bin ROUNDS\n", argv[0]); return 2; } + FILE *f = fopen(argv[1], "rb"); + if (!f) return 1; + static unsigned char bytes[1 << 20]; + size_t size = fread(bytes, 1, sizeof(bytes), f); + fclose(f); + int rounds = atoi(argv[2]); + size_t rows = size / (8 * FIELDS), chunks = (rows + RUN - 1) / RUN; + /* The same rows laid out as leaves (what a sender would put on the wire). */ + Leaf *leaves = calloc(chunks, sizeof(Leaf)); + for (size_t r = 0; r < rows; ++r) + for (unsigned g = 0; g < FIELDS; ++g) memcpy(&leaves[r / RUN].cells[g][r % RUN], bytes + 8 * (FIELDS * r + g), 8); + const Leaf **kept = calloc(chunks, sizeof(*kept)); + PureList *list = pure_list_new(FIELDS, kinds); + if (!list || !pure_list_arrays(list)) return 1; + /* Both read the same: each row's fields. */ + if (pure_list_read(list, bytes, size) != PURE_OK || pure_list_count(list) != rows || + take_leaves(leaves, chunks, rows, kept) != rows) + return 1; + for (size_t r = 0; r < rows; ++r) + for (unsigned g = 0; g < FIELDS; ++g) + if (pure_list_get(list, r, g).num != kept[r / RUN]->cells[g][r % RUN]) { fprintf(stderr, "row %zu differs\n", r); return 1; } + int ins = counter(PERF_COUNT_HW_INSTRUCTIONS), cyc = counter(PERF_COUNT_HW_CPU_CYCLES); + if (ins < 0 || cyc < 0) return 1; + uint64_t today_ins = 0, today_cyc = 0, leaves_ins = 0, leaves_cyc = 0; + for (int k = 0; k < rounds; ++k) { + pure_list_clear(list); + ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0); + ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0); + PureResult read = pure_list_read(list, bytes, size); + ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0); + if (read != PURE_OK) return 1; + today_ins += read_counter(ins), today_cyc += read_counter(cyc); + ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0); + ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0); + size_t taken = take_leaves(leaves, chunks, rows, kept); + ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0); + if (taken != rows) return 1; + leaves_ins += read_counter(ins), leaves_cyc += read_counter(cyc); + } + double n = (double)rows * rounds; + printf("{\"rows\":%zu,\"fields\":%d,\"chunks\":%zu,\"rounds\":%d,\"today_instructions_per_row\":%.1f,\"today_cycles_per_row\":%.1f," + "\"leaves_instructions_per_row\":%.1f,\"leaves_cycles_per_row\":%.1f,\"answers\":\"every cell the same both ways\"}\n", + rows, FIELDS, chunks, rounds, today_ins / n, today_cyc / n, leaves_ins / n, leaves_cyc / n); + return 0; +} |
