aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research/140-spec-suite.md
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 17:54:16 +0100
committereunhhu <[email protected]>2026-10-18 17:54:16 +0100
commit9f17dd667c3282aa28d6b3961eda28e592961478 (patch)
treedb954434bdfde3186a06192f1b7ae4e93c44453c /docs/research/140-spec-suite.md
parentd5f0cac74721863a91b0a799256b62c0b7b2a3c1 (diff)
downloadpure-9f17dd667c3282aa28d6b3961eda28e592961478.tar.gz
pure-9f17dd667c3282aa28d6b3961eda28e592961478.zip
Research 140, 027's stage 3 measured: a board of columns of cards of tags (aup's stored_nested) alike in memory and on a store restarting eight times, every step's fault, states and a view (2,400 snapshots, 54 restarts); killed at the tree's four stages and the log's two, all or nothing; replacement moving nested lists by name at depth; appending into a stored item's list costs about 170 ns an item it already holds (append_cost.c: 42 µs an add on average over the first 500, 738 µs over 8,000), building one by appends quadratic; instruction counts: the first commit's BFS +1.45% with batches and +1.68% without, from branches in the hot steps, gone with the load's internal ops (b29 against b27: BFS 0.000%/+0.034%, the others within 0.008%; the bank's queries -0.25% to +0.22%); what remains (a message's rows holding lists, a list field's .count in a fold, for over xs[i].f); 027's stage 3 as made (the representation chosen against the plan, its cost); 008's wording (kind 5 in images' headers alone); CHECKPOINT's direction row through stage 3
Diffstat (limited to 'docs/research/140-spec-suite.md')
-rw-r--r--docs/research/140-spec-suite.md60
1 files changed, 60 insertions, 0 deletions
diff --git a/docs/research/140-spec-suite.md b/docs/research/140-spec-suite.md
index 13d8849..44f4e9f 100644
--- a/docs/research/140-spec-suite.md
+++ b/docs/research/140-spec-suite.md
@@ -364,6 +364,66 @@
**2단계의 막대 전체.** JSON 파서가 `y_`를 모두 받고 `n_`을 모두 거절한다(지남). `i_`는 정한 쪽을 적었다. 줄 수는 못 미쳤다(위).
시간은 적었다. TPC-C의 모듈과 은행 질의의 명령 수는 그대로다(지남).
+## 027의 3단계: 중첩한 저장 상태 (2026-10-18)
+
+3단계에서 더한 것:
+- **Pure.**
+ - 저장 상태와 transient의 리스트가 리스트를 담은 레코드를 받는다. 메시지의 행은 아직 받지 않고, 리스트 필드는 키가 되지 않는다.
+ - 그런 칸은 리스트 값을 그 항목들의 저장 형태로 담는다. 항목마다 필드의 차례로, 수·bool·i64는 8바이트, text와 리스트는 u32le
+ 길이와 바이트(리스트는 다시 이 형태)다.
+ - ITEM_GET은 그것을 이벤트의 리스트 하나로 풀고(읽은 바이트를 이벤트의 몫에 센다), LIST_APPEND·LIST_INSERT·ITEM_SET은 이벤트의
+ 리스트에서 짓는다. APPEND_IN은 풀고, 덧붙이고, 다시 쓴다(이벤트와 함께 되돌려진다).
+ - 이미지의 머리가 레코드 그래프를 적는다. 시작과 교체는 그래프를 따라 필드 이름으로 깊이까지 옮긴다.
+- **aup.**
+ - 저장 항목의 리스트에 덧붙이기(`columns[i].cards.append(...)`), let의 리스트·`[]`·리스트 값을 필드에 쓰기
+ - 같은 레코드의 항목을 그대로 덧붙이기(`ys.append(y)`)
+- **그대로인 것.** 리스트 모듈은 그 칸을 text의 바이트처럼 다룬다. 그래서 이미지, 로그, 되돌리기, 읽는 쪽과의 나눔, 커밋의 원자성,
+ 죽음의 회복이 깊이에서도 그대로다. 새 저장소 형식도 새 복구 길도 없다.
+
+**맞음.** aup의 검사 `stored_nested`가 보드(열 → 카드 → 태그, 카드의 태그를 읽는 보기 하나)를 무작위 걸음으로 돌린다.
+- 같은 걸음을 메모리 앱과 저장 앱에 준다. 저장 앱은 실행 하나에 여덟 번 재시작하고, 실행의 절반은 몰아서 저장한다.
+- 씨앗 여섯, 걸음 200개다. 매 걸음의 실패, 상태, 보기가 모두 같다(스냅숏 2,400개, 재시작 54번).
+
+**죽음.** Pure의 단위 시험 `nested_crashes`가 중첩 리스트를 바꾸는 커밋의 도중에 프로세스를 죽인다.
+- 트리의 네 단계와 로그의 두 단계에서 죽인다. 다시 열면 매번 그 커밋 전부이거나 아무것도 아니다.
+- 실패한 커밋은 아무것도 바꾸지 않는다.
+
+**이름 잇기.** 단위 시험 `nested_replace`가 저장 리스트와 transient 리스트의 중첩 리스트를 교체로 옮긴다.
+- 보드의 필드 차례가 바뀌고 잎의 레코드에 필드(`w = 7`)가 더해진다. 값은 이름으로 깊이까지 옮겨지고, 새 필드는 기본값을 받는다.
+- 이 단위 시험들은 ASan·UBSan 빌드로도 지났다.
+
+**막대.**
+
+| 막대 | 잰 것 | |
+|---|---|---|
+| 메모리 대 재시작하는 저장 앱 | 매 걸음 같음(스냅숏 2,400개, 재시작 54번) | 지남 |
+| 커밋의 모든 단계에서 죽임 | 여섯 단계 모두 전부 아니면 없음 | 지남 |
+| 기존 일의 명령 수 | 아래(막대 1%) | 지남 |
+
+**비용.** 중첩 리스트를 바꿀 때마다 그 값 전체를 풀고 짓고 쓴다. 바뀐 크기가 아니라 그 값의 크기에 비례한다.
+- 한 열에 카드를 하나씩 덧붙이면(메모리), 처음 n번의 덧붙이기 평균이 500에서 42 µs, 1,000에서 84 µs, 2,000에서 171 µs, 4,000에서
+ 349 µs, 8,000에서 738 µs다. 덧붙이기 한 번이 이미 있는 카드 하나에 170 ns 남짓이다. 그래서 n개를 덧붙여 짓는 것은 제곱이다.
+- 구조를 나누는 표현(노드를 나누는 트리, 경로로 이름 붙인 로그)은 뜻을 바꾸지 않고 나중에 고를 수 있는 구현이다(027).
+- 원본: `research/experiments/044-specs/append_cost.c`, `2026-10-18-stage3-append.jsonl`.
+
+**기존 일의 명령 수(3단계).**
+- 첫 커밋(d8e2974, b28)에서 BFS가 묶음 +1.45%, 실행기만 +1.68%로 막대를 넘었다. 나머지 넷은 ±0.01% 안이었다.
+ - 원인은 셋이었다. `pure_cell_text`의 비교가 늘었고, `text_field`가 리스트 필드를 알게 되었다(BFS의 덧붙이기마다 필드마다 두 번
+ 묻는다). 셋째는 뜨거운 경우들 안의 새 갈래였다.
+ - 앞의 둘을 되돌리니 일부만 돌아왔다. 남은 것은 갈래가 `step`의 코드 생성을 옮긴 값이었다.
+- 고친 커밋(d5f0cac, b29)은 그런 칸을 다루는 명령을 로드가 내부 명령(OP_KEPT_*)으로 바꿔 쓴다. 검증과 계획이 끝난 뒤에 바꾸므로
+ 모듈은 그 명령을 담지 않는다. JIT는 그 블록을 실행기에 맡긴다. 그래서 다른 명령의 걸음은 3단계 전과 같다.
+- b29 대 b27(2c): BFS 묶음 0.000%, 실행기만 +0.034%. join +0.008%, knapsack +0.001%, matmul +0.001%, roots 0.000%.
+- 은행 질의(s27 대 s29, 각 네 번): per_branch −0.14%, in_credit −0.25%, large +0.12%, ranks −0.08%, none 0.00%, field +0.22%,
+ found +0.15%(막대 ±1%).
+- 원본: `041-batches/2026-10-18-counts.jsonl`(b28, b29), `040-against-c/2026-10-18-counts-s28.jsonl`, `-s28b.jsonl`, `-s29.jsonl`,
+ `-s29b.jsonl`.
+
+**남은 것.**
+- 메시지의 행이 리스트를 담는 것(JSON 경계에서 바로 트리로)은 4단계다.
+- 접기 안에서 리스트 필드의 `.count`를 읽는 것(`sum(c.cards.count for c in cols)`)은 거절된다. 접기는 레지스터를 묶지 않기 때문이다.
+- `for c in columns[i].cards`는 구문이 받지 않는다. `let col = columns[i]`로 한 번 거친다.
+
## 착수 기준 (다음 일)
- **바꿀 결정.** 계산의 핵을 설계한다(027). 위 표의 여덟 후보 가운데 무엇을 어떤 모양으로 받을지 정한다.