aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research
diff options
context:
space:
mode:
Diffstat (limited to 'docs/research')
-rw-r--r--docs/research/134-batch-plans-stage-one.md72
1 files changed, 72 insertions, 0 deletions
diff --git a/docs/research/134-batch-plans-stage-one.md b/docs/research/134-batch-plans-stage-one.md
new file mode 100644
index 0000000..9426a65
--- /dev/null
+++ b/docs/research/134-batch-plans-stage-one.md
@@ -0,0 +1,72 @@
+# 134 — 묶음 프로그램 1단계: 블록의 사실을 로드 때 한 번
+
+2026-10-08. [025](../design/025-batch-programs.md)의 1단계다. 묶음 루프마다 블록을 로드 때 한 번 읽어 계획을 만들고, 묶음은
+그 계획을 본다. 코드는 `c112238`(쓰는 지역 변수, 항목, 칸에 더하기, 명령마다의 몫, 모으기의 앞)과 `140cc46`(점화식)이다.
+잰 것은 `b10` = `140cc46`을 `b9` = `735ba81`과 번갈아 돈 것이다(`041-batches/runs.sh pairs`, 11번의 중앙값을 다섯 번씩, 그 중앙값).
+
+## 쉬운 말로
+
+- **막대에 못 미쳤다.** 배낭의 묶음마다 값이 0.90에서 0.68 µs로 4분의 1 줄었다(막대 ≤ 0.60). 배낭은 11.48에서 10.34 ms다(막대
+ ≤ 10.3). sum0의 묶음마다 명령은 2,330에서 1,822다(막대 ≤ 1,550). 025는 블록 다시 읽기를 묶음마다의 3분의 1 남짓으로 셈했는데,
+ 그 셈이 `pass`의 명령마다 머리(`arrive`, 고르기의 끝, 레인이 없는지)를 "찾기"에 넣었다. 그것들은 그대로 남는다.
+- **남은 0.68 µs는 2단계의 몫이다.** 명령마다 `Val`을 쌓고(지역 변수, 필드, 행), 값이 정수인지 묻고(`whole`), 경계를 보고(`rows_in`),
+ 읽기를 기록하고(`read_of`), 키의 첫 행을 찾는다(`find`). 블록을 다시 읽는 일은 거의 다 없어졌다.
+- **다른 일은 그대로다.** matmul은 40.0에서 38.2 ms가 됐지만 명령 수가 같다(놓인 자리). BFS는 24.04에서 24.31 ms인데 명령은
+ 0.2% 적다(놓인 자리). join, sort, words는 같다. 실행기만의 길은 명령 수가 그대로다.
+
+## 잰 것
+
+| 일 | `b9` | `b10` | 레인 256 (`b10`) | 명령 (`b9` → `b10`, 프로세스 전체) |
+|---|---:|---:|---:|---:|
+| knapsack | 11.48 ms | **10.34** | 20.24 (`b9` 24.56) | −4.3% |
+| matmul | 40.01 | 38.19 | 38.27 | −0.04% |
+| roots | 8.97 | 8.81 | 10.45 | −1.7% |
+| join | 116.95 | 117.04 | 118.78 | −1.3% |
+| BFS | 24.04 | 24.31 | 25.66 | −0.2% |
+| sort | 57.30 | 56.95 | | |
+| words | 215.8 | 218.8 | | |
+
+- **배낭의 묶음마다**: (20.24 − 10.34) / 14,490 묶음 = 0.68 µs(`b9` 0.90).
+- **sum0과 ops0의 묶음마다**(`icount`, 세 번의 3,000과 12,000 묶음): sum0 2,330 → 1,822 명령, 805 → 597 사이클; ops0 2,103 → 1,658
+ 명령, 717 → 521 사이클.
+- **sum0의 시간은 8.69에서 6.13 ms다.** 그러나 대부분은 1단계의 몫이 아니다. 모으기의 부분합을 놓는 루프(`reduction`)를 `b9`의
+ 컴파일러는 레인 하나씩 스칼라로 짰고(요소당 명령 5), `b10`에서는 같은 소스를 넷씩 벡터로 짰다. 묶음 구조체가 바뀌어 인라인의
+ 자리가 달라진 값이다. 3단계(모으기를 꼴에 맞게)에서 이 루프 자체를 없애거나 늘 벡터가 되게 한다.
+- 실행기만의 길(`PURE_BATCH=0`)은 다섯 일 모두 명령 수가 같다. 사이클은 BFS +3.9%, join +1.3%로 오갔다(같은 명령: 놓인 자리).
+
+원본: `041-batches/2026-10-08-pairs.jsonl`, `2026-10-08-counts.jsonl`, `2026-10-08-batch-costs.jsonl`.
+
+## 만든 것
+
+- **계획**(`PureBatchPlan`, `module-batch.c`). 모듈을 다 읽은 뒤(명령의 계획 비트가 모두 정해진 뒤) 묶음 루프마다 하나. 쓰는 지역
+ 변수와 항목, 칸에 더하기, 점화식(갱신, 걸음, 기록, 한 회차의 동작, 남는 수의 자리), 블록의 명령마다 몫(칸에 더하기의 again·add·set,
+ 점화식의 갱신·기록, 모으기의 앞까지의 거리). 모듈은 루프의 차례로 계획을 쥐고, 묶음은 이분 탐색으로 찾는다. 점화식이 없는 루프의
+ 계획은 블록 길이 × 4 바이트에 수백 바이트다.
+- **남는 수는 출처로.** 점화식의 상수·필드·LOAD·블록이 쓰지 않는 지역 변수는 출처마다 한 자리이고, 묶음을 열 때 값을 채운다. 전에는
+ 값의 비트로 자리를 나눴다(값이 같으면 한 자리). 두 출처의 값이 우연히 같을 때만 다르고, 답은 같다.
+- **견줌**(`PURE_BATCH_CHECK`, 빌드 플래그). 만드는 동안 묶음마다 블록을 옛 방식으로 다시 읽어 계획과 견주고, 다르면 멈추게 했다.
+ 무작위 시드와 고정 소스, 단위 시험이 이 견줌을 지난 뒤 지웠다.
+- **레인 수 플래그**(`PURE_BATCH_LANES`). 묶음마다의 값을 1024와 256의 차로 재려고, 소스를 고치지 않고 빌드한다.
+- `041-batches/runs.sh pairs`: 두 빌드를 번갈아 잰다.
+
+## 남은 묶음마다 (`b10`, 배낭, 레인 256의 덧값)
+
+| 무엇 | 줄 |
+|---|---|
+| 지역 변수를 쌓기(`local_load`의 push), 필드·행을 균일 값으로 쌓기 | 1246, 2007, 2052 |
+| 정수인지 묻기(`whole`, `whole32`), 경계(`rows_in`) | 228, 229, 628 |
+| 4바이트 연산의 준비(`word_op`), 지역 변수 쓰기(`local_store`) | 499, 507, 919 |
+| 읽기의 기록(`read_of`, `reads_set`), 키의 첫 행(`find`), 칸(`cells_at`) | 718, 719, 2444, 759, 813, 656 |
+| 명령마다의 머리와 디스패치(`switch`, 몫, 같은 코드의 값) | 1964–1987, 2001 |
+| 미룬 쓰기의 확인(`settle`) | 2565 |
+
+명령 하나에 약 80 사이클이다. 2단계는 이것을 정적 꼴(균일·아핀은 스칼라 걸음), 묶음에 한 번 가져오는 리스트의 사실, 묶음을 열 때
+고르는 커널로 줄인다.
+
+## 뜻과 맞음
+
+- 견줌 빌드에서 시드 1, 2, 3, 5, 23의 무작위 프로그램(기본과 `PURE_SELECT=all`), 고정 소스 여섯의 세 길(두 모드), 단위 시험 세 모드가
+ 지났다. 계획이 옛 셈과 어긋난 일은 없었다.
+- 견줌을 지운 빌드에서 단위 시험 세 모드, ASan 단위 시험(기본과 실행기 모드), sanitized `pure-run`의 고정 소스(두 모드)와 시드 23의
+ 프로그램(두 모드), Pure의 `make check`, aup의 검사 두 모드가 지났다.
+- TPC-C와 은행 질의는 재지 않았다. 실행기(`run`)는 건드리지 않았고, 묶음 루프가 없는 길의 명령 수가 같음을 위에서 셌다.