aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments')
-rw-r--r--research/experiments/044-specs/2026-10-13-stage2a.jsonl8
-rw-r--r--research/experiments/044-specs/json_check.py115
-rw-r--r--research/experiments/044-specs/json_time.c132
3 files changed, 255 insertions, 0 deletions
diff --git a/research/experiments/044-specs/2026-10-13-stage2a.jsonl b/research/experiments/044-specs/2026-10-13-stage2a.jsonl
new file mode 100644
index 0000000..e536b54
--- /dev/null
+++ b/research/experiments/044-specs/2026-10-13-stage2a.jsonl
@@ -0,0 +1,8 @@
+{"size":103781,"jit":"0","batch":"0","c_ns_per_byte":2.567,"pure_ns_per_byte":298.133,"ratio":116.1,"instructions":35226511,"instructions_per_byte":339.4}
+{"size":103781,"jit":"0","batch":"","c_ns_per_byte":2.567,"pure_ns_per_byte":298.362,"ratio":116.2,"instructions":35226511,"instructions_per_byte":339.4}
+{"size":103781,"jit":"all","batch":"","c_ns_per_byte":2.569,"pure_ns_per_byte":298.453,"ratio":116.2,"instructions":35226511,"instructions_per_byte":339.4}
+{"size":103781,"jit":"","batch":"","c_ns_per_byte":2.571,"pure_ns_per_byte":299.164,"ratio":116.4,"instructions":35226511,"instructions_per_byte":339.4}
+{"size":103781,"python_ns_per_byte":247.0}
+{"size":65536,"jit":"0","batch":"0","c_ns_per_byte":9.025,"pure_ns_per_byte":1437.304,"ratio":159.3,"instructions":16887501,"instructions_per_block":16476}
+{"size":65536,"jit":"","batch":"","c_ns_per_byte":9.051,"pure_ns_per_byte":1439.504,"ratio":159.0,"instructions":16887501,"instructions_per_block":16476}
+{"messages": 142, "right": {"batches": 142, "rounds": 142, "all": 142, "default": 142}, "aup_lines": 42, "aup_brace_lines": 5, "python_lines": 28}
diff --git a/research/experiments/044-specs/json_check.py b/research/experiments/044-specs/json_check.py
new file mode 100644
index 0000000..b5d62f0
--- /dev/null
+++ b/research/experiments/044-specs/json_check.py
@@ -0,0 +1,115 @@
+# Whether bytes are one JSON text (RFC 8259): the design of aup's examples/json.aup in plain Python, the reference
+# 140 counts its lines against (read once from the start, a stack of the arrays and objects open).
+def space(b):
+ return b in (0x20, 0x09, 0x0a, 0x0d)
+
+
+def digit(b):
+ return 0x30 <= b <= 0x39
+
+
+def hexdigit(b):
+ return digit(b) or 0x61 <= b <= 0x66 or 0x41 <= b <= 0x46
+
+
+def valid(d):
+ try:
+ t = d.decode('utf-8')
+ except UnicodeDecodeError:
+ return False
+ n, i, mode, ok, open_ = len(d), 0, 0, True, [] # mode 0: a value, 1: after one, 2: a key, 3: a key or }, 4: a value or ]
+ while ok:
+ while i < n and space(d[i]):
+ i += 1
+ if i == n:
+ return mode == 1 and not open_
+ b = d[i]
+ if mode == 1:
+ if not open_:
+ ok = False
+ elif b == 0x2c:
+ mode = 0 if open_[-1] == 0x5b else 2
+ elif b == open_[-1] + 2:
+ open_.pop()
+ else:
+ ok = False
+ i += 1
+ elif mode == 3 and b == 0x7d or mode == 4 and b == 0x5d:
+ open_.pop()
+ mode = 1
+ i += 1
+ elif mode in (2, 3):
+ ok = b == 0x22
+ mode = 5
+ elif b in (0x5b, 0x7b):
+ open_.append(b)
+ mode = 4 if b == 0x5b else 3
+ i += 1
+ elif b == 0x22 or mode == 5:
+ i += 1
+ while i < n and d[i] != 0x22 and ok:
+ if d[i] < 0x20:
+ ok = False
+ elif d[i] == 0x5c and i + 1 < n and d[i + 1] == 0x75:
+ ok = i + 5 < n and all(hexdigit(x) for x in d[i + 2:i + 6])
+ i += 6
+ elif d[i] == 0x5c:
+ ok = i + 1 < n and d[i + 1] in b'"\\/bfnrt'
+ i += 2
+ else:
+ i += 1
+ ok = ok and i < n
+ i += 1
+ if mode == 5:
+ while i < n and space(d[i]):
+ i += 1
+ ok = ok and i < n and d[i] == 0x3a
+ i += 1
+ mode = 0
+ else:
+ mode = 1
+ elif b == 0x2d or digit(b):
+ if b == 0x2d:
+ i += 1
+ ok = i < n and digit(d[i])
+ if ok and d[i] == 0x30:
+ i += 1
+ else:
+ while i < n and digit(d[i]):
+ i += 1
+ if ok and i < n and d[i] == 0x2e:
+ i += 1
+ ok = i < n and digit(d[i])
+ while i < n and digit(d[i]):
+ i += 1
+ if ok and i < n and d[i] in (0x65, 0x45):
+ i += 1
+ if i < n and d[i] in (0x2b, 0x2d):
+ i += 1
+ ok = i < n and digit(d[i])
+ while i < n and digit(d[i]):
+ i += 1
+ mode = 1
+ else:
+ word = b'true' if b == 0x74 else b'false' if b == 0x66 else b'null'
+ ok = d[i:i + len(word)] == word
+ i += len(word)
+ mode = 1
+ return False
+
+
+if __name__ == '__main__':
+ import os, sys, time
+ suite = sys.argv[1]
+ files = sorted(os.listdir(suite))
+ data = [open(os.path.join(suite, f), 'rb').read() for f in files]
+ for f, d in zip(files, data):
+ assert not f.startswith('y_') or valid(d), f
+ assert not f.startswith('n_') or not valid(d), f
+ big = b'[' + b','.join(b'{"key%d": [1.5e3, true, null, "text \\u00e9 %d"]}' % (k, k) for k in range(2000)) + b']'
+ runs = []
+ for _ in range(7):
+ start = time.perf_counter()
+ valid(big)
+ runs.append((time.perf_counter() - start) * 1e9 / len(big))
+ print('{"size":%d,"python_ns_per_byte":%.1f}' % (len(big), sorted(runs)[3]))
diff --git a/research/experiments/044-specs/json_time.c b/research/experiments/044-specs/json_time.c
new file mode 100644
index 0000000..b401ece
--- /dev/null
+++ b/research/experiments/044-specs/json_time.c
@@ -0,0 +1,132 @@
+/* Research 140, design 027 stage 2a: aup's examples/json.aup (run by Pure)
+ * against the same validator in C, on the same document: both must say it is
+ * JSON, then each is timed (the median of seven runs of rounds checks).
+ *
+ * cc -O2 -std=c17 -I../../../core json_time.c ../../../build/libpure.a -lm -pthread -o json_time
+ * ./json_time json.pmod ROUNDS (PURE_JIT and PURE_BATCH choose Pure's path)
+ *
+ * The document is json_check.py's: 2,000 objects of a key and an array of a
+ * number, true, null and a string with an escape. One JSON line. */
+#define _POSIX_C_SOURCE 200809L
+#include "module.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static int space(int b) { return b == 0x20 || b == 0x09 || b == 0x0a || b == 0x0d; }
+static int digit(int b) { return b >= 0x30 && b <= 0x39; }
+static int hex(int b) { return digit(b) || (b >= 0x61 && b <= 0x66) || (b >= 0x41 && b <= 0x46); }
+
+/* json.aup's design in C (its UTF-8 check left out: the document is ASCII). */
+static int valid(const unsigned char *d, size_t n, unsigned char *open) {
+ size_t i = 0, depth = 0;
+ int mode = 0, ok = 1;
+ while (ok) {
+ while (i < n && space(d[i])) ++i;
+ if (i == n) return mode == 1 && !depth;
+ int b = d[i];
+ if (mode == 1) {
+ if (!depth) ok = 0;
+ else if (b == 0x2c) mode = open[depth - 1] == 0x5b ? 0 : 2;
+ else if (b == open[depth - 1] + 2) --depth;
+ else ok = 0;
+ ++i;
+ } else if ((mode == 3 && b == 0x7d) || (mode == 4 && b == 0x5d)) {
+ --depth, mode = 1, ++i;
+ } else if (mode == 2 || mode == 3) {
+ ok = b == 0x22, mode = 5;
+ } else if (b == 0x5b || b == 0x7b) {
+ open[depth++] = (unsigned char)b, mode = b == 0x5b ? 4 : 3, ++i;
+ } else if (b == 0x22 || mode == 5) {
+ ++i;
+ while (i < n && d[i] != 0x22 && ok) {
+ if (d[i] < 0x20) ok = 0;
+ else if (d[i] == 0x5c && i + 1 < n && d[i + 1] == 0x75) {
+ ok = i + 5 < n && hex(d[i + 2]) && hex(d[i + 3]) && hex(d[i + 4]) && hex(d[i + 5]);
+ i += 6;
+ } else if (d[i] == 0x5c) {
+ ok = i + 1 < n && strchr("\"\\/bfnrt", d[i + 1]) && d[i + 1];
+ i += 2;
+ } else ++i;
+ }
+ ok = ok && i < n, ++i;
+ if (mode == 5) {
+ while (i < n && space(d[i])) ++i;
+ ok = ok && i < n && d[i] == 0x3a, ++i, mode = 0;
+ } else mode = 1;
+ } else if (b == 0x2d || digit(b)) {
+ if (b == 0x2d) ++i;
+ ok = i < n && digit(d[i]);
+ if (ok && d[i] == 0x30) ++i;
+ else while (i < n && digit(d[i])) ++i;
+ if (ok && i < n && d[i] == 0x2e) {
+ ++i, ok = i < n && digit(d[i]);
+ while (i < n && digit(d[i])) ++i;
+ }
+ if (ok && i < n && (d[i] == 0x65 || d[i] == 0x45)) {
+ ++i;
+ if (i < n && (d[i] == 0x2b || d[i] == 0x2d)) ++i;
+ ok = i < n && digit(d[i]);
+ while (i < n && digit(d[i])) ++i;
+ }
+ mode = 1;
+ } else {
+ const char *word = b == 0x74 ? "true" : b == 0x66 ? "false" : "null";
+ size_t length = strlen(word);
+ ok = i + length <= n && !memcmp(d + i, word, length), i += length, mode = 1;
+ }
+ }
+ return 0;
+}
+
+static double now_ns(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
+}
+
+static int by_value(const void *a, const void *b) {
+ double x = *(const double *)a, y = *(const double *)b;
+ return (x > y) - (x < y);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 3) { fprintf(stderr, "usage: %s MODULE ROUNDS\n", argv[0]); return 2; }
+ int rounds = atoi(argv[2]);
+ static unsigned char doc[1 << 18], open[1 << 18];
+ size_t n = 0;
+ doc[n++] = '[';
+ for (int k = 0; k < 2000; ++k)
+ n += (size_t)sprintf((char *)doc + n, "%s{\"key%d\": [1.5e3, true, null, \"text \\u00e9 %d\"]}", k ? "," : "", k, k);
+ doc[n++] = ']';
+ PureModule module;
+ char error[160];
+ if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ PureApp app;
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int check = pure_module_input(&module, "check");
+ if (pure_app_message(&app, (uint32_t)check, NULL, -1, (PureSpan){doc, n}) != PURE_OK) { fprintf(stderr, "event failed\n"); return 1; }
+ uint64_t work = app.work;
+ if (app.values[0].num != 1 || !valid(doc, n, open)) { fprintf(stderr, "not both valid\n"); return 1; }
+ double c_runs[7], pure_runs[7];
+ volatile int sink = 0;
+ for (int r = 0; r < 7; ++r) {
+ double start = now_ns();
+ for (int i = 0; i < rounds; ++i) sink += valid(doc, n, open);
+ c_runs[r] = (now_ns() - start) / rounds;
+ start = now_ns();
+ for (int i = 0; i < rounds; ++i)
+ if (pure_app_message(&app, (uint32_t)check, NULL, -1, (PureSpan){doc, n}) != PURE_OK) return 1;
+ pure_runs[r] = (now_ns() - start) / rounds;
+ }
+ qsort(c_runs, 7, sizeof(double), by_value);
+ qsort(pure_runs, 7, sizeof(double), by_value);
+ const char *jit = getenv("PURE_JIT"), *batch = getenv("PURE_BATCH");
+ printf("{\"size\":%zu,\"jit\":\"%s\",\"batch\":\"%s\",\"c_ns_per_byte\":%.3f,\"pure_ns_per_byte\":%.3f,\"ratio\":%.1f,"
+ "\"instructions\":%llu,\"instructions_per_byte\":%.1f}\n",
+ n, jit ? jit : "", batch ? batch : "", c_runs[3] / (double)n, pure_runs[3] / (double)n, pure_runs[3] / c_runs[3],
+ (unsigned long long)work, (double)work / (double)n);
+ pure_app_stop(&app);
+ return 0;
+}