aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research/134-batch-plans-stage-one.md
blob: 9426a65df3840cb13de560ca4f6b4e63ec6b1c2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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`)는 건드리지 않았고, 묶음 루프가 없는 길의 명령 수가 같음을 위에서 셌다.