aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/040-against-c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-05 13:24:59 +0100
committereunhhu <[email protected]>2026-10-05 13:24:59 +0100
commit6b4580607efa698abdc07718f00c6cbdb425c210 (patch)
tree0cad9cddfd332140ecce47d17e439573f32ea11e /research/experiments/040-against-c
parent5a3466e8ff4d8f3c4271719b90858b7d08697dcd (diff)
downloadpure-6b4580607efa698abdc07718f00c6cbdb425c210.tar.gz
pure-6b4580607efa698abdc07718f00c6cbdb425c210.zip
Research 124's suite: seven kinds of work in one event each (roots, knapsack, sort, join, words, matmul, BFS), each against the C one would write, in one process with the same inputs and answers; PURE_AHEAD=0 to measure the code without reading ahead
Diffstat (limited to 'research/experiments/040-against-c')
-rw-r--r--research/experiments/040-against-c/join.aup46
-rw-r--r--research/experiments/040-against-c/knapsack.aup39
-rw-r--r--research/experiments/040-against-c/matmul.aup37
-rw-r--r--research/experiments/040-against-c/roots.aup23
-rw-r--r--research/experiments/040-against-c/runs.sh38
-rw-r--r--research/experiments/040-against-c/sort.aup34
-rw-r--r--research/experiments/040-against-c/suite-run.c318
-rw-r--r--research/experiments/040-against-c/words.aup33
8 files changed, 568 insertions, 0 deletions
diff --git a/research/experiments/040-against-c/join.aup b/research/experiments/040-against-c/join.aup
new file mode 100644
index 0000000..bddda80
--- /dev/null
+++ b/research/experiments/040-against-c/join.aup
@@ -0,0 +1,46 @@
+# A join (research 124's suite): customers with ids drawn by MINSTD from
+# seed 1 (sparse, distinct), each in one of 16 regions; orders each of a
+# customer drawn by index and an amount; the amounts summed by the region of
+# each order's customer, found by its id.
+record Customer {
+ id = 0
+ region = 0
+}
+record Total {
+ region = 0
+ amount = 0
+}
+state top = 0
+state sum = 0
+input go = message {
+ customers: u32
+ orders: u32
+}
+on go {
+ let all: list of Customer
+ let byid: list of Customer by id
+ let x = 1
+ for i in 0 .. go.customers {
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ all.append(Customer { id: x, region: i - floor(i / 16) * 16 })
+ byid.add(Customer { id: x, region: i - floor(i / 16) * 16 })
+ }
+ let totals: list of Total by region
+ for r in 0 .. 16 {
+ totals.add(Total { region: r })
+ }
+ for o in 0 .. go.orders {
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ let c = all.first + (x - floor(x / go.customers) * go.customers)
+ let region = byid[c.id].region
+ totals[region].amount = totals[region].amount + (x - floor(x / 1000) * 1000)
+ }
+ let most = 0
+ let all_amounts = 0
+ for t in totals {
+ most = max(most, t.amount)
+ all_amounts = all_amounts + t.amount
+ }
+ top = most
+ sum = all_amounts
+}
diff --git a/research/experiments/040-against-c/knapsack.aup b/research/experiments/040-against-c/knapsack.aup
new file mode 100644
index 0000000..3375bf4
--- /dev/null
+++ b/research/experiments/040-against-c/knapsack.aup
@@ -0,0 +1,39 @@
+# Whole numbers in a table (research 124's suite): the best worth a
+# knapsack of the given capacity holds of n items, each item's weight and
+# worth drawn by MINSTD (x * 48271 mod 2^31 - 1) from seed 1.
+record Item {
+ weight = 0
+ worth = 0
+}
+record Cell {
+ at = 0
+ best = 0
+}
+state best = 0
+input go = message {
+ n: u32
+ capacity: u32
+}
+on go {
+ let items: list of Item
+ let x = 1
+ for i in 0 .. go.n {
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ let w = x - floor(x / 1000) * 1000 + 1
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ let v = x - floor(x / 1000) * 1000
+ items.append(Item { weight: w, worth: v })
+ }
+ let table: list of Cell by at
+ for c in 0 .. go.capacity + 1 {
+ table.add(Cell { at: c })
+ }
+ for it in items {
+ for k in 0 .. go.capacity + 1 - it.weight {
+ let c = go.capacity - k
+ let with = table[c - it.weight].best + it.worth
+ if with > table[c].best { table[c].best = with }
+ }
+ }
+ best = table[go.capacity].best
+}
diff --git a/research/experiments/040-against-c/matmul.aup b/research/experiments/040-against-c/matmul.aup
new file mode 100644
index 0000000..ae75aa5
--- /dev/null
+++ b/research/experiments/040-against-c/matmul.aup
@@ -0,0 +1,37 @@
+# Numbers in a grid (research 124's suite): two n by n matrices of whole
+# numbers below 100 drawn by MINSTD from seed 1, kept by row * n + column,
+# multiplied: the sum of the product's cells and its last cell.
+record Cell {
+ at = 0
+ v = 0
+}
+state sum = 0
+state corner = 0
+input go = message {
+ n: u32
+}
+on go {
+ let a: list of Cell by at
+ let b: list of Cell by at
+ let x = 1
+ for i in 0 .. go.n * go.n {
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ a.add(Cell { at: i, v: x - floor(x / 100) * 100 })
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ b.add(Cell { at: i, v: x - floor(x / 100) * 100 })
+ }
+ let total = 0
+ let last = 0
+ for i in 0 .. go.n {
+ for j in 0 .. go.n {
+ let s = 0
+ for k in 0 .. go.n {
+ s = s + a[i * go.n + k].v * b[k * go.n + j].v
+ }
+ total = total + s
+ last = s
+ }
+ }
+ sum = total
+ corner = last
+}
diff --git a/research/experiments/040-against-c/roots.aup b/research/experiments/040-against-c/roots.aup
new file mode 100644
index 0000000..22c9189
--- /dev/null
+++ b/research/experiments/040-against-c/roots.aup
@@ -0,0 +1,23 @@
+# Numbers in a loop (research 124's suite): the square roots of 1 .. n by
+# Newton's steps, each until a step stops shrinking, and their sum.
+state total = 0
+state steps = 0
+input go = message {
+ n: u32
+}
+on go {
+ let sum = 0
+ let count = 0
+ for i in 1 .. go.n + 1 {
+ let r = i
+ for s in 0 .. 64 {
+ let next = (r + i / r) / 2
+ if next >= r { break }
+ r = next
+ count = count + 1
+ }
+ sum = sum + r
+ }
+ total = sum
+ steps = count
+}
diff --git a/research/experiments/040-against-c/runs.sh b/research/experiments/040-against-c/runs.sh
new file mode 100644
index 0000000..3c83cf4
--- /dev/null
+++ b/research/experiments/040-against-c/runs.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# The runs of research 124's suite (docs/design/023), from the repository's
+# root, each under research/guard.py on a quiet Pi:
+# runs.sh build NAME COMMIT suite-run on COMMIT's core, and the
+# workloads' modules, in ~/.cache/pure-040/NAME
+# runs.sh suite NAME ROUNDS every workload on NAME: as it runs (JIT where
+# Linux AArch64), the executor alone
+# (PURE_JIT=0), and BFS without reading ahead
+# (PURE_AHEAD=0)
+set -e
+here=research/experiments/040-against-c
+builds=$HOME/.cache/pure-040
+day=$(date +%F)
+quiet() { until awk '{ exit !($1 < 1.5) }' /proc/loadavg; do sleep 10; done; }
+case "$1" in
+build)
+ rm -rf "$builds/src-$2"
+ git worktree add -f -q --detach "$builds/src-$2" "$3"
+ make -s -C "$builds/src-$2" build/libpure.a
+ mkdir -p "$builds/$2"
+ cc -O2 -g -std=c17 -I"$builds/src-$2/core" $here/suite-run.c "$builds/src-$2/build/libpure.a" -lm -o "$builds/$2/suite-run"
+ git worktree remove --force "$builds/src-$2"
+ for work in roots knapsack sort join words matmul; do ../aup/build/aup build $here/$work.aup -o "$builds/$2/$work.pmod"; done
+ ../aup/build/aup build research/experiments/033-computing/graph.aup -o "$builds/$2/graph.pmod" ;;
+suite)
+ quiet
+ echo "$2 $(uptime)" >> "$here/$day-load.txt"
+ python3 research/guard.py --most 3072 -- "$builds/$2/suite-run" "$builds/$2" "$3" \
+ | sed "s/^{/{\"version\":\"$2\",\"jit\":1,\"ahead\":1,/" >> "$here/$day-suite.jsonl"
+ quiet
+ PURE_JIT=0 python3 research/guard.py --most 3072 -- "$builds/$2/suite-run" "$builds/$2" "$3" \
+ | sed "s/^{/{\"version\":\"$2\",\"jit\":0,\"ahead\":1,/" >> "$here/$day-suite.jsonl"
+ quiet
+ PURE_AHEAD=0 python3 research/guard.py --most 3072 -- "$builds/$2/suite-run" "$builds/$2" "$3" bfs bfs_ahead \
+ | sed "s/^{/{\"version\":\"$2\",\"jit\":1,\"ahead\":0,/" >> "$here/$day-suite.jsonl" ;;
+*) echo "runs.sh build NAME COMMIT | suite NAME ROUNDS" >&2
+ exit 2 ;;
+esac
diff --git a/research/experiments/040-against-c/sort.aup b/research/experiments/040-against-c/sort.aup
new file mode 100644
index 0000000..967b500
--- /dev/null
+++ b/research/experiments/040-against-c/sort.aup
@@ -0,0 +1,34 @@
+# Sorting (research 124's suite): n numbers drawn by MINSTD from seed 1
+# (distinct: its period is 2^31 - 2) into a list kept by them, then read in
+# order: the smallest, the middle one, the largest, and how many follow a
+# larger one (none when sorted).
+record Key {
+ k = 0
+}
+state first = 0
+state middle = 0
+state last = 0
+state disorder = 0
+input go = message {
+ n: u32
+}
+on go {
+ let keys: list of Key by k
+ let x = 1
+ for i in 0 .. go.n {
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ keys.add(Key { k: x })
+ }
+ let at = 0
+ let before = -1
+ let wrong = 0
+ for e in keys {
+ if e.k < before { wrong = wrong + 1 }
+ if at == floor(go.n / 2) { middle = e.k }
+ before = e.k
+ at = at + 1
+ }
+ first = keys.first.k
+ last = keys.last.k
+ disorder = wrong
+}
diff --git a/research/experiments/040-against-c/suite-run.c b/research/experiments/040-against-c/suite-run.c
new file mode 100644
index 0000000..3efbf94
--- /dev/null
+++ b/research/experiments/040-against-c/suite-run.c
@@ -0,0 +1,318 @@
+/* Research 124's suite: kinds of work in one event each, Pure against C in
+ * this process on the same machine, the same inputs (each program draws them
+ * by MINSTD, x * 48271 mod 2^31 - 1, from seed 1, as its C does), the same
+ * answers (checked every round). The C is what one would write for the task:
+ * the same algorithm, its natural types (whole numbers as integers), qsort,
+ * a hash table with linear probing, snprintf.
+ *
+ * suite-run DIR ROUNDS [NAME...] a JSON line per round of each workload
+ * (Pure's ms, C's ms), then per workload the medians and Pure / C. DIR holds
+ * the workloads' modules (NAME.pmod; bfs: 033's graph.aup as graph.pmod;
+ * bfs_ahead: the same search against bfs.c read ahead, for context).
+ * Without NAMEs, all of them. PURE_JIT and PURE_AHEAD as the core reads them. */
+#define _POSIX_C_SOURCE 200809L
+#include "module.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static double now_ms(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
+}
+
+static int compare_doubles(const void *a, const void *b) {
+ double x = *(const double *)a, y = *(const double *)b;
+ return (x > y) - (x < y);
+}
+
+static uint64_t minstd(uint64_t x) { return x * 48271 % 2147483647; }
+
+/* ---- The C of each workload: its answers in out[] */
+
+static void c_roots(const uint32_t *in, double *out) {
+ double sum = 0, count = 0;
+ for (uint32_t i = 1; i <= in[0]; ++i) {
+ double r = i;
+ for (int s = 0; s < 64; ++s) {
+ double next = (r + i / r) / 2;
+ if (next >= r) break;
+ r = next;
+ ++count;
+ }
+ sum += r;
+ }
+ out[0] = sum, out[1] = count;
+}
+
+static void c_knapsack(const uint32_t *in, double *out) {
+ uint32_t n = in[0], capacity = in[1];
+ uint32_t *weight = malloc(4 * (size_t)n), *worth = malloc(4 * (size_t)n);
+ int64_t *best = calloc((size_t)capacity + 1, 8);
+ uint64_t x = 1;
+ for (uint32_t i = 0; i < n; ++i) {
+ x = minstd(x), weight[i] = (uint32_t)(x % 1000) + 1;
+ x = minstd(x), worth[i] = (uint32_t)(x % 1000);
+ }
+ for (uint32_t i = 0; i < n; ++i)
+ for (uint32_t c = capacity; c >= weight[i] && c <= capacity; --c) {
+ int64_t with = best[c - weight[i]] + worth[i];
+ if (with > best[c]) best[c] = with;
+ }
+ out[0] = (double)best[capacity];
+ free(weight), free(worth), free(best);
+}
+
+static int compare_u32(const void *a, const void *b) {
+ uint32_t x = *(const uint32_t *)a, y = *(const uint32_t *)b;
+ return (x > y) - (x < y);
+}
+
+static void c_sort(const uint32_t *in, double *out) {
+ uint32_t n = in[0];
+ uint32_t *keys = malloc(4 * (size_t)n);
+ uint64_t x = 1;
+ for (uint32_t i = 0; i < n; ++i) x = minstd(x), keys[i] = (uint32_t)x;
+ qsort(keys, n, 4, compare_u32);
+ uint32_t wrong = 0;
+ for (uint32_t i = 1; i < n; ++i) wrong += keys[i] < keys[i - 1];
+ out[0] = keys[0], out[1] = keys[n / 2], out[2] = keys[n - 1], out[3] = wrong;
+ free(keys);
+}
+
+static uint32_t hash32(uint32_t x) { return x * 2654435761u; }
+
+static void c_join(const uint32_t *in, double *out) {
+ uint32_t customers = in[0], orders = in[1], size = 1;
+ while (size < 2 * customers) size *= 2;
+ uint32_t *ids = malloc(4 * (size_t)customers), *keys = calloc(size, 4), *regions = malloc(4 * (size_t)size);
+ uint64_t x = 1, totals[16] = {0};
+ for (uint32_t i = 0; i < customers; ++i) {
+ x = minstd(x), ids[i] = (uint32_t)x;
+ uint32_t at = hash32(ids[i]) & (size - 1);
+ while (keys[at]) at = (at + 1) & (size - 1);
+ keys[at] = ids[i], regions[at] = i % 16;
+ }
+ for (uint32_t o = 0; o < orders; ++o) {
+ x = minstd(x);
+ uint32_t id = ids[x % customers], at = hash32(id) & (size - 1);
+ while (keys[at] != id) at = (at + 1) & (size - 1);
+ totals[regions[at]] += x % 1000;
+ }
+ uint64_t most = 0, sum = 0;
+ for (int r = 0; r < 16; ++r) most = totals[r] > most ? totals[r] : most, sum += totals[r];
+ out[0] = (double)most, out[1] = (double)sum;
+ free(ids), free(keys), free(regions);
+}
+
+static uint32_t fnv(const char *s) {
+ uint32_t h = 2166136261u;
+ for (; *s; ++s) h = (h ^ (unsigned char)*s) * 16777619u;
+ return h;
+}
+
+static void c_words(const uint32_t *in, double *out) {
+ uint32_t n = in[0], vocabulary = in[1], size = 1;
+ while (size < 2 * vocabulary) size *= 2;
+ char (*words)[16] = calloc(size, 16);
+ uint32_t *counts = calloc(size, 4), distinct = 0, most = 0;
+ uint64_t x = 1;
+ for (uint32_t i = 0; i < n; ++i) {
+ x = minstd(x);
+ char w[16];
+ snprintf(w, sizeof(w), "w%u", (unsigned)(x % vocabulary));
+ uint32_t at = fnv(w) & (size - 1);
+ while (counts[at] && strcmp(words[at], w)) at = (at + 1) & (size - 1);
+ if (!counts[at]) memcpy(words[at], w, sizeof(w)), ++distinct;
+ if (++counts[at] > most) most = counts[at];
+ }
+ out[0] = distinct, out[1] = most;
+ free(words), free(counts);
+}
+
+static void c_matmul(const uint32_t *in, double *out) {
+ uint32_t n = in[0];
+ double *a = malloc(8 * (size_t)n * n), *b = malloc(8 * (size_t)n * n), total = 0, last = 0;
+ uint64_t x = 1;
+ for (uint32_t i = 0; i < n * n; ++i) {
+ x = minstd(x), a[i] = (double)(x % 100);
+ x = minstd(x), b[i] = (double)(x % 100);
+ }
+ for (uint32_t i = 0; i < n; ++i)
+ for (uint32_t j = 0; j < n; ++j) {
+ double s = 0;
+ for (uint32_t k = 0; k < n; ++k) s += a[i * n + k] * b[k * n + j];
+ total += s, last = s;
+ }
+ out[0] = total, out[1] = last;
+ free(a), free(b);
+}
+
+/* ---- BFS: 033's graph (bfs-run's generator, seed 121), bfs.c's loop */
+
+typedef struct { uint32_t nodes, *start, *to, *queue; int32_t *depth; } Graph;
+static Graph graph;
+static uint64_t graph_state = 121;
+static uint64_t graph_next(void) {
+ graph_state ^= graph_state << 13, graph_state ^= graph_state >> 7, graph_state ^= graph_state << 17;
+ return graph_state;
+}
+static int compare_u64(const void *a, const void *b) {
+ uint64_t x = *(const uint64_t *)a, y = *(const uint64_t *)b;
+ return (x > y) - (x < y);
+}
+
+static void c_bfs(const uint32_t *in, double *out) {
+ const Graph *g = &graph;
+ for (uint32_t v = 0; v < g->nodes; ++v) g->depth[v] = -1;
+ uint32_t head = 0, tail = 0;
+ g->queue[tail++] = in[0];
+ g->depth[in[0]] = 0;
+ while (head < tail) {
+ uint32_t v = g->queue[head++];
+ for (uint32_t k = g->start[v]; k < g->start[v + 1]; ++k)
+ if (g->depth[g->to[k]] < 0) g->depth[g->to[k]] = g->depth[v] + 1, g->queue[tail++] = g->to[k];
+ }
+ out[0] = g->depth[in[1]], out[1] = tail;
+}
+
+/* bfs.c read ahead as 022's floor reads Pure's (bfsfloor's c_ahead): the
+ * offsets of the node 16 on in the queue, the first edge of the one 8 on
+ * (context for Pure's reading ahead, not the C it is held to). */
+static void c_bfs_ahead(const uint32_t *in, double *out) {
+ const Graph *g = &graph;
+ for (uint32_t v = 0; v < g->nodes; ++v) g->depth[v] = -1;
+ uint32_t head = 0, tail = 0;
+ g->queue[tail++] = in[0];
+ g->depth[in[0]] = 0;
+ while (head < tail) {
+ if (head + 16 < tail) __builtin_prefetch(&g->start[g->queue[head + 16]]);
+ if (head + 8 < tail) __builtin_prefetch(&g->to[g->start[g->queue[head + 8]]]);
+ uint32_t v = g->queue[head++];
+ for (uint32_t k = g->start[v]; k < g->start[v + 1]; ++k)
+ if (g->depth[g->to[k]] < 0) g->depth[g->to[k]] = g->depth[v] + 1, g->queue[tail++] = g->to[k];
+ }
+ out[0] = g->depth[in[1]], out[1] = tail;
+}
+
+/* The graph into the app (size, then the edges as rows) and into C's adjacency. */
+static bool load_graph(PureApp *app, const PureModule *m, uint32_t nodes, size_t edges) {
+ graph_state = 121; /* the same graph each time */
+ free(graph.start), free(graph.to), free(graph.queue), free(graph.depth);
+ uint64_t *keys = malloc(edges * sizeof(*keys));
+ size_t count = 0;
+ while (count < edges) {
+ uint64_t a = graph_next() % nodes, b = graph_next() % nodes;
+ if (a != b) keys[count++] = a << 20 | b;
+ }
+ qsort(keys, count, sizeof(*keys), compare_u64);
+ size_t unique = 0;
+ for (size_t i = 0; i < count; ++i)
+ if (!unique || keys[i] != keys[unique - 1]) keys[unique++] = keys[i];
+ graph.nodes = nodes;
+ graph.start = calloc((size_t)nodes + 1, 4), graph.to = malloc(4 * unique), graph.queue = malloc(4ull * nodes);
+ graph.depth = malloc(4ull * nodes);
+ for (size_t i = 0; i < unique; ++i) ++graph.start[(keys[i] >> 20) + 1], graph.to[i] = (uint32_t)(keys[i] & 0xFFFFF);
+ for (uint32_t v = 0; v < nodes; ++v) graph.start[v + 1] += graph.start[v];
+ unsigned char four[4];
+ memcpy(four, &nodes, 4);
+ if (pure_app_message(app, (uint32_t)pure_module_input(m, "size"), NULL, -1, (PureSpan){four, 4}) != PURE_OK) return false;
+ enum { CHUNK = 65536 };
+ double *rows = malloc(CHUNK * 2 * sizeof(*rows));
+ for (size_t at = 0; at < unique; at += CHUNK) {
+ size_t n = unique - at < CHUNK ? unique - at : CHUNK;
+ for (size_t k = 0; k < n; ++k) rows[2 * k] = (double)keys[at + k], rows[2 * k + 1] = (double)(keys[at + k] & 0xFFFFF);
+ if (pure_app_message(app, (uint32_t)pure_module_input(m, "load"), NULL, -1, (PureSpan){(unsigned char *)rows, n * 16}) !=
+ PURE_OK)
+ return false;
+ }
+ free(rows), free(keys);
+ return true;
+}
+
+/* ---- The workloads */
+
+typedef struct {
+ const char *name, *input;
+ uint32_t args[2];
+ unsigned argc;
+ const char *answers[4];
+ void (*c)(const uint32_t *in, double *out);
+} Work;
+
+static const Work works[] = {
+ {"roots", "go", {100000}, 1, {"total", "steps"}, c_roots},
+ {"knapsack", "go", {100, 50000}, 2, {"best"}, c_knapsack},
+ {"sort", "go", {200000}, 1, {"first", "middle", "last", "disorder"}, c_sort},
+ {"join", "go", {100000, 1000000}, 2, {"top", "sum"}, c_join},
+ {"words", "go", {300000, 50000}, 2, {"distinct", "most"}, c_words},
+ {"matmul", "go", {200}, 1, {"sum", "corner"}, c_matmul},
+ {"bfs", "route", {0, 0}, 2, {"hops", "reached"}, c_bfs},
+ {"bfs_ahead", "route", {0, 0}, 2, {"hops", "reached"}, c_bfs_ahead},
+};
+
+static int run(const char *dir, const Work *w, unsigned rounds) {
+ char path[512];
+ bool bfs = !strncmp(w->name, "bfs", 3);
+ snprintf(path, sizeof(path), "%s/%s.pmod", dir, bfs ? "graph" : w->name);
+ static PureModule module;
+ static PureApp app;
+ char error[160];
+ if (pure_module_read(path, &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s: %s\n", path, error); return 1; }
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int input = pure_module_input(&module, w->input), states[4];
+ unsigned answers = 0;
+ while (answers < 4 && w->answers[answers]) states[answers] = pure_module_state(&module, w->answers[answers]), ++answers;
+ if (bfs && !load_graph(&app, &module, 100000, 1000000)) { fputs("bfs: load failed\n", stderr); return 1; }
+ double *pure_ms = malloc(rounds * sizeof(double)), *c_ms = malloc(rounds * sizeof(double));
+ bool agree = true;
+ for (unsigned r = 0; r < rounds; ++r) {
+ uint32_t args[2] = {w->args[0], w->args[1]};
+ if (bfs) args[0] = (uint32_t)(graph_next() % 100000), args[1] = (uint32_t)(graph_next() % 100000);
+ unsigned char bytes[8];
+ memcpy(bytes, args, 4 * w->argc);
+ double a = now_ms();
+ PureResult result = pure_app_message(&app, (uint32_t)input, NULL, -1, (PureSpan){bytes, 4 * w->argc});
+ double b = now_ms();
+ if (result != PURE_OK) { fprintf(stderr, "%s failed: %s\n", w->name, pure_fault_name(app.fault)); return 1; }
+ double out[4] = {0};
+ w->c(args, out);
+ double c = now_ms();
+ pure_ms[r] = b - a, c_ms[r] = c - b;
+ bool same = true;
+ for (unsigned i = 0; i < answers; ++i) same = same && app.values[states[i]].num == out[i];
+ agree = agree && same;
+ printf("{\"work\":\"%s\",\"round\":%u,\"pure_ms\":%.3f,\"c_ms\":%.3f,\"same\":%s,\"answers\":[", w->name, r, pure_ms[r],
+ c_ms[r], same ? "true" : "false");
+ for (unsigned i = 0; i < answers; ++i) printf("%s%.17g", i ? "," : "", app.values[states[i]].num);
+ printf("],\"c\":[");
+ for (unsigned i = 0; i < answers; ++i) printf("%s%.17g", i ? "," : "", out[i]);
+ printf("]}\n");
+ fflush(stdout);
+ }
+ unsigned skip = bfs ? 1 : 0; /* bfs: the first search packs the edges (022) */
+ qsort(pure_ms + skip, rounds - skip, sizeof(double), compare_doubles);
+ qsort(c_ms, rounds, sizeof(double), compare_doubles);
+ double p = pure_ms[skip + (rounds - skip) / 2], cm = c_ms[rounds / 2];
+ printf("{\"work\":\"%s\",\"pure_median_ms\":%.3f,\"c_median_ms\":%.3f,\"ratio\":%.3f,\"agree\":%s}\n", w->name, p, cm, p / cm,
+ agree ? "true" : "false");
+ pure_app_stop(&app);
+ free(pure_ms), free(c_ms);
+ return agree ? 0 : 1;
+}
+
+int main(int argc, char **argv) {
+ if (argc < 3) { fputs("suite-run DIR ROUNDS [NAME...]\n", stderr); return 2; }
+ unsigned rounds = (unsigned)atoi(argv[2]);
+ int failed = 0;
+ for (size_t i = 0; i < sizeof(works) / sizeof(*works); ++i) {
+ bool wanted = argc == 3;
+ for (int a = 3; a < argc; ++a) wanted = wanted || !strcmp(argv[a], works[i].name);
+ if (wanted) failed |= run(argv[1], &works[i], rounds);
+ }
+ return failed;
+}
diff --git a/research/experiments/040-against-c/words.aup b/research/experiments/040-against-c/words.aup
new file mode 100644
index 0000000..af24130
--- /dev/null
+++ b/research/experiments/040-against-c/words.aup
@@ -0,0 +1,33 @@
+# Text (research 124's suite): n words "w0" .. "w{vocabulary - 1}", each
+# drawn by MINSTD from seed 1, counted in a list kept by the word: how many
+# distinct, and the most any one is seen. A let holds no text (aup), so the
+# word is written where it is used.
+record Count {
+ word: text
+ n = 0
+}
+state distinct = 0
+state most = 0
+input go = message {
+ n: u32
+ vocabulary: u32
+}
+on go {
+ let counts: list of Count by word
+ let x = 1
+ for i in 0 .. go.n {
+ x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647
+ let n = x - floor(x / go.vocabulary) * go.vocabulary
+ if counts.has("w{n}") {
+ counts["w{n}"].n = counts["w{n}"].n + 1
+ } else {
+ counts.add(Count { word: "w{n}", n: 1 })
+ }
+ }
+ distinct = counts.count
+ let m = 0
+ for c in counts {
+ m = max(m, c.n)
+ }
+ most = m
+}