aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/006-native-save-latency
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /research/experiments/006-native-save-latency
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'research/experiments/006-native-save-latency')
-rw-r--r--research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json221
-rw-r--r--research/experiments/006-native-save-latency/measure.py139
2 files changed, 360 insertions, 0 deletions
diff --git a/research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json b/research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json
new file mode 100644
index 0000000..0f2d215
--- /dev/null
+++ b/research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json
@@ -0,0 +1,221 @@
+{
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "filesystem": "/dev/mmcblk0p2[/home/eunhhu/work/pure] ext4",
+ "workload": "one-byte final-character replacement, Ctrl+S to complete visible frame; includes full draft comparison, runtime, durable store and redraw; initial creation and final independent CLI read excluded",
+ "rounds": 3,
+ "edits_per_session": 8,
+ "binaries": {
+ "flat": {
+ "cli_sha256": "27d9ad1c83265cef24f5e756537a68692852145bfc7c1e31255e18c08fac6295",
+ "tui_sha256": "e8b224e3ce5f5d83d597418d4027ec36dd44bfe84d17f6e9224135dc457316c1"
+ },
+ "paged": {
+ "cli_sha256": "e829bd6bad3b56bc319cdab03aba7f45c44afab53876e462ae57c35b82823e9a",
+ "tui_sha256": "09303ec33d07c12b07834b07fb5eeb836005c23fcb3a6b6cc2683631a9d4b13c"
+ }
+ },
+ "runs": {
+ "flat": {
+ "256": [
+ {
+ "samples_ns": [
+ 5958081,
+ 7778966,
+ 6548524,
+ 19995479,
+ 62301450,
+ 9400518,
+ 5842210,
+ 8589502
+ ],
+ "median_ns": 8184234,
+ "final_revision": 9,
+ "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb"
+ },
+ {
+ "samples_ns": [
+ 9415093,
+ 16809059,
+ 8447428,
+ 8676279,
+ 6705838,
+ 9542795,
+ 10029758,
+ 6036581
+ ],
+ "median_ns": 9045686,
+ "final_revision": 9,
+ "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb"
+ },
+ {
+ "samples_ns": [
+ 7148893,
+ 10145461,
+ 7778114,
+ 6517728,
+ 62411765,
+ 50067253,
+ 7390504,
+ 8495113
+ ],
+ "median_ns": 8136613,
+ "final_revision": 9,
+ "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb"
+ }
+ ],
+ "1048576": [
+ {
+ "samples_ns": [
+ 145172249,
+ 96882586,
+ 40365568,
+ 349602121,
+ 39360237,
+ 89495933,
+ 183181933,
+ 87081031
+ ],
+ "median_ns": 93189259,
+ "final_revision": 9,
+ "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56"
+ },
+ {
+ "samples_ns": [
+ 29222924,
+ 88900009,
+ 92498076,
+ 104674699,
+ 38498924,
+ 95164515,
+ 104447385,
+ 98216731
+ ],
+ "median_ns": 93831295,
+ "final_revision": 9,
+ "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56"
+ },
+ {
+ "samples_ns": [
+ 150963793,
+ 89303082,
+ 89762414,
+ 128623245,
+ 37319148,
+ 93848499,
+ 101872780,
+ 90491636
+ ],
+ "median_ns": 92170067,
+ "final_revision": 9,
+ "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56"
+ }
+ ]
+ },
+ "paged": {
+ "256": [
+ {
+ "samples_ns": [
+ 8496650,
+ 19415609,
+ 6801246,
+ 9666703,
+ 10048962,
+ 6038507,
+ 8610260,
+ 7698300
+ ],
+ "median_ns": 8553455,
+ "final_revision": 9,
+ "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb"
+ },
+ {
+ "samples_ns": [
+ 9062278,
+ 62254117,
+ 6872783,
+ 9583444,
+ 50943122,
+ 6743320,
+ 9396814,
+ 9949258
+ ],
+ "median_ns": 9490129,
+ "final_revision": 9,
+ "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb"
+ },
+ {
+ "samples_ns": [
+ 9526426,
+ 8758853,
+ 6706653,
+ 97037325,
+ 5886063,
+ 20592941,
+ 9999906,
+ 6661357
+ ],
+ "median_ns": 9142639,
+ "final_revision": 9,
+ "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb"
+ }
+ ],
+ "1048576": [
+ {
+ "samples_ns": [
+ 20936773,
+ 33867655,
+ 20611237,
+ 21735235,
+ 20170997,
+ 34975172,
+ 20189182,
+ 22094974
+ ],
+ "median_ns": 21336004,
+ "final_revision": 9,
+ "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56"
+ },
+ {
+ "samples_ns": [
+ 97702566,
+ 25086172,
+ 20087812,
+ 22047179,
+ 53326394,
+ 22037808,
+ 20212422,
+ 21726179
+ ],
+ "median_ns": 22042493,
+ "final_revision": 9,
+ "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56"
+ },
+ {
+ "samples_ns": [
+ 19868442,
+ 22179308,
+ 21243699,
+ 30931198,
+ 21662716,
+ 68731271,
+ 23544638,
+ 26258818
+ ],
+ "median_ns": 22861973,
+ "final_revision": 9,
+ "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56"
+ }
+ ]
+ }
+ },
+ "median_ns": {
+ "flat": {
+ "256": 8542307,
+ "1048576": 93173287
+ },
+ "paged": {
+ "256": 9229546,
+ "1048576": 22042493
+ }
+ }
+}
diff --git a/research/experiments/006-native-save-latency/measure.py b/research/experiments/006-native-save-latency/measure.py
new file mode 100644
index 0000000..f1e2a00
--- /dev/null
+++ b/research/experiments/006-native-save-latency/measure.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python3
+"""Time durable one-byte saves in the actual native terminal document app."""
+import argparse
+import fcntl
+import hashlib
+import json
+import os
+from pathlib import Path
+import platform
+import pty
+import select
+import statistics
+import struct
+import subprocess
+import tempfile
+import termios
+import time
+
+
+def run(*args):
+ return subprocess.run([str(arg) for arg in args], check=True,
+ capture_output=True, timeout=30)
+
+
+def frame(master, process):
+ output = bytearray()
+ deadline = time.monotonic() + 15
+ while time.monotonic() < deadline:
+ if select.select([master], [], [], 0.5)[0]:
+ output.extend(os.read(master, 65536))
+ if b'\x1b[?25h' in output:
+ return bytes(output)
+ if process.poll() is not None:
+ break
+ raise AssertionError((process.poll(), output[-1000:]))
+
+
+def one_session(cli, tui, root, size, iterations):
+ root.mkdir(parents=True)
+ store = root / 'store'
+ body = b'A' * size
+ insert = root / 'initial.bin'
+ insert.write_bytes(body)
+ assert json.loads(run(cli, '--store', store, 'edit', 'document',
+ 0, 0, 0, insert).stdout)['revision'] == '1'
+ master, slave = pty.openpty()
+ fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 24, 80, 0, 0))
+ original = termios.tcgetattr(slave)
+ process = subprocess.Popen([str(tui), '--store', str(store), 'document'],
+ stdin=slave, stdout=slave, stderr=slave,
+ env={**os.environ, 'LANG': 'C.UTF-8', 'LC_ALL': 'C.UTF-8'})
+ samples = []
+ try:
+ assert b'Pure | document' in frame(master, process)
+ for index in range(iterations):
+ replacement = b'B' if index % 2 == 0 else b'A'
+ os.write(master, b'\x7f')
+ frame(master, process)
+ os.write(master, replacement)
+ frame(master, process)
+ start = time.perf_counter_ns()
+ os.write(master, b'\x13')
+ shown = frame(master, process)
+ elapsed = time.perf_counter_ns() - start
+ expected = f'Saved 1-byte edit at byte {size - 1}'.encode()
+ revision = f'revision {index + 2}'.encode()
+ assert expected in shown and revision in shown, shown[-1500:]
+ samples.append(elapsed)
+ os.write(master, b'\x11')
+ process.wait(timeout=8)
+ assert process.returncode == 0
+ assert termios.tcgetattr(slave) == original
+ finally:
+ if process.poll() is None:
+ process.kill()
+ process.wait(timeout=5)
+ os.close(master)
+ os.close(slave)
+ final = run(cli, '--store', store, 'show', 'document')
+ expected_body = body[:-1] + (b'B' if iterations % 2 else b'A')
+ assert final.stdout == expected_body
+ assert json.loads(final.stderr)['revision'] == str(iterations + 1)
+ return {'samples_ns': samples, 'median_ns': int(statistics.median(samples)),
+ 'final_revision': iterations + 1,
+ 'body_sha256': hashlib.sha256(final.stdout).hexdigest()}
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--flat-cli', type=Path, required=True)
+ parser.add_argument('--flat-tui', type=Path, required=True)
+ parser.add_argument('--paged-cli', type=Path, required=True)
+ parser.add_argument('--paged-tui', type=Path, required=True)
+ parser.add_argument('--scratch-parent', type=Path, required=True)
+ parser.add_argument('--rounds', type=int, default=3)
+ parser.add_argument('--edits', type=int, default=8)
+ parser.add_argument('--output', type=Path)
+ args = parser.parse_args()
+ if args.rounds < 1 or args.edits < 1 or (args.output and args.output.exists()):
+ parser.error('rounds and edits must be positive; output must not exist')
+ binaries = {name: (cli.resolve(), tui.resolve()) for name, cli, tui in
+ (('flat', args.flat_cli, args.flat_tui),
+ ('paged', args.paged_cli, args.paged_tui))}
+ parent = args.scratch_parent.resolve()
+ parent.mkdir(parents=True, exist_ok=True)
+ filesystem = run('findmnt', '-T', parent, '-no', 'SOURCE,FSTYPE').stdout.decode().strip()
+ raw = {name: {'256': [], '1048576': []} for name in binaries}
+ with tempfile.TemporaryDirectory(prefix='pure-native-save-', dir=parent) as directory:
+ root = Path(directory)
+ for size in (256, 1_048_576):
+ for round_number in range(args.rounds):
+ order = ('flat', 'paged') if round_number % 2 == 0 else ('paged', 'flat')
+ for name in order:
+ cli, tui = binaries[name]
+ raw[name][str(size)].append(one_session(
+ cli, tui, root / f'{size}-{round_number}-{name}', size, args.edits))
+ result = {'platform': platform.platform(), 'filesystem': filesystem,
+ 'workload': 'one-byte final-character replacement, Ctrl+S to complete visible frame; '
+ 'includes full draft comparison, runtime, durable store and redraw; '
+ 'initial creation and final independent CLI read excluded',
+ 'rounds': args.rounds, 'edits_per_session': args.edits,
+ 'binaries': {name: {'cli_sha256': hashlib.sha256(cli.read_bytes()).hexdigest(),
+ 'tui_sha256': hashlib.sha256(tui.read_bytes()).hexdigest()}
+ for name, (cli, tui) in binaries.items()},
+ 'runs': raw,
+ 'median_ns': {name: {size: int(statistics.median(
+ sample for session in sessions for sample in session['samples_ns']))
+ for size, sessions in sizes.items()} for name, sizes in raw.items()}}
+ encoded = json.dumps(result, indent=2) + '\n'
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(encoded)
+ print(json.dumps({'platform': result['platform'], 'filesystem': filesystem,
+ 'median_ns': result['median_ns'], 'rounds': args.rounds,
+ 'edits_per_session': args.edits}))
+
+
+if __name__ == '__main__':
+ main()