aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-05 23:22:55 +0100
committereunhhu <[email protected]>2026-10-05 23:22:55 +0100
commitfffe01904984aebd10b237e400859b5ff86f0729 (patch)
tree907c53f1f55956bbcd7e1c71afc64fa1cfca2899 /docs
parent2609cbc2347c2e709652c3d0d6f44e52dafba194 (diff)
downloadpure-fffe01904984aebd10b237e400859b5ff86f0729.tar.gz
pure-fffe01904984aebd10b237e400859b5ff86f0729.zip
Research 127: the batch executor's stage 2 (nested loops' rounds run together lane by lane, breaks, reductions folded in the rounds' order) and the measured passes after it (finite checks in the arithmetic's loop, tests as four-byte masks, the loop's value made once): roots 9.13 ms without machine code, below the tier's 9.57; matmul 50 (the tier 14.3: its table's loop and 15 instructions a nested round left); 685 random programs alike; 024 and CHECKPOINT say so
Diffstat (limited to 'docs')
-rw-r--r--docs/design/024-batch-executor.md2
-rw-r--r--docs/research/127-batch-executor-stage-two.md65
2 files changed, 66 insertions, 1 deletions
diff --git a/docs/design/024-batch-executor.md b/docs/design/024-batch-executor.md
index 92e065a..6a59f7d 100644
--- a/docs/design/024-batch-executor.md
+++ b/docs/design/024-batch-executor.md
@@ -1,6 +1,6 @@
# 024 — 묶음 실행기: 기계어 없이 루프를 C 가까이
-작성일: 2026-10-05. 상태: 제안과 시제품([041](../../research/experiments/041-batches/)). 1단계를 만들었다([126](../research/126-batch-executor-stage-one.md): knapsack이 실행기에서 1,041 ms → 13.6 ms, 안쪽 루프는 계층의 1.17배로 기준 미달).
+작성일: 2026-10-05. 상태: 제안과 시제품([041](../../research/experiments/041-batches/)). 1단계를 만들었다([126](../research/126-batch-executor-stage-one.md): knapsack이 실행기에서 1,041 ms → 13.6 ms, 안쪽 루프는 계층의 1.17배로 기준 미달). 2단계(중첩 루프를 함께, 모으기)를 만들었다([127](../research/127-batch-executor-stage-two.md): roots 9.13 ms로 계층 9.57보다 빠름, matmul 50 ms).
[023](023-optimizing-tier.md)의 계층은 루프 묶음을 SSA로 올려 AArch64 기계어를 실행 시간에 만든다. 일곱 가지 일의 기하 평균을
C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-steps.md)), 실행 가능한 메모리에 코드를 쓰고 리눅스 AArch64에서만
diff --git a/docs/research/127-batch-executor-stage-two.md b/docs/research/127-batch-executor-stage-two.md
new file mode 100644
index 0000000..1b361ee
--- /dev/null
+++ b/docs/research/127-batch-executor-stage-two.md
@@ -0,0 +1,65 @@
+# 127 — 묶음 실행기 2단계: 중첩 루프를 함께, 모으기, 그리고 roots가 기계어를 넘다
+
+2026-10-05. [024](../design/024-batch-executor.md)의 2단계와 그 뒤의 다듬기(`4a39bf0`, `bec7ce6`). [126](126-batch-executor-stage-one.md)과
+같은 방법으로 쟀다(`041-batches/runs.sh`, 빌드 `b2` = `bec7ce6`, 11번의 중앙값).
+
+## 쉬운 말로
+
+- **roots가 기계어 없이 계층보다 빠르다.** 실행기만으로 130.6 ms인 roots가 묶음으로 9.13 ms다. 023의 계층(기계어)은 9.57 ms, C는
+ 6.86 ms다. 기계어를 만들지 않는 실행기가 이 일에서 계층을 넘은 것은 처음이다.
+- **matmul은 914.9 ms에서 50.0 ms가 됐다.** 계층은 14.3 ms라 아직 3.5배다. 남은 것은 둘이다. 하나는 행렬을 채우는 루프로, 점화식이
+ 있어 묶지 못하고 실행기에서 약 12 ms가 든다. 다른 하나는 안쪽 루프의 회차마다 명령 15개를 해석하는 값이다(레인이 200개뿐이다).
+- **바뀐 것.** 묶음의 몸체 안 루프가 레인마다 함께 돈다. 회차 t는 회차가 t보다 많고 빠져나가지 않은 레인들이 돈다. `BREAK`는 레인마다
+ 빠져나가게 한다. `x = x + e`, `min`, `max`는 모으기다. 레인마다 제 몫을 모으고, 묶음의 끝에서 회차 차례대로 합친다.
+- **묶음 루프가 없는 모듈의 실행기는 그대로다.** join과 sort의 명령 수는 묶음을 켜도 같다(76.69억, 15.02억).
+
+## 묶음
+
+| 일 | 기계어 (JIT) | 묶음 (`PURE_JIT=0`) | 실행기만 | C | 묶음의 명령 / 실행기만 |
+|---|---:|---:|---:|---:|---:|
+| roots | 9.57 ms | **9.13** | 130.6 | 6.86 | 1.73억 / 27.56억 |
+| knapsack | 7.52 | 13.80 | 1,040.8 | 6.22 | 4.65억 / 216.88억 |
+| matmul | 14.27 | 50.03 | 914.9 | 7.55 | 12.06억 / 214.36억 |
+| join | 78.6 | 424.8 | 424.7 | 29.3 | 같다 |
+| sort | 36.7 | 78.2 | 79.1 | 26.9 | 같다 |
+
+## 만든 것
+
+- **함께 도는 중첩 루프.** 묶음의 몸체에 있는 `LOOP`의 회차를 레인마다 함께 돈다. 레인마다 회차 수가 다르면(`for k in 0 .. i`)
+ 회차 t의 마스크는 회차가 남은 레인이다. `BREAK`를 탄 레인은 그 루프의 남은 회차에서 빠지고, 루프가 끝나면 다른 레인과 함께 바깥
+ 몸체를 이어 간다. 슬롯은 회차가 열릴 때 쓰인다(회차 0에서는 아래 끝 그대로라서 −0도 그대로다). 안쪽 회차마다 그 루프의 작업량
+ (블록 길이 + 1)을 레인마다 센다. 묶음의 끝에서 남은 예산과 견주고, 넘으면 아무것도 바꾸지 않고 실행기에 넘긴다. 한 겹까지만 묶는다.
+- **모으기.** 몸체에서 `x = x op e`(op는 `+`, `min`, `max`)로만 읽고 쓰는 지역 변수는 모으기다. 레인마다 op의 항등원에서 시작한다.
+ 합의 항등원은 −0이다(−0 + e는 e다). 끝에서 회차 차례대로 합친다. 한 레인이 한 번만 더하면 회차마다 더한 것과 비트까지 같다. 여러
+ 번 더하는 레인이 있으면, 모두 정수이고 2^53 안일 때만 받는다(그래야 묶는 차례가 답을 바꾸지 않는다). 아니면 묶음이 회차들을
+ 돌려준다.
+- **계획.** 로드 때 지역 변수마다 그 루프의 범위에서 "확실히 쓴 뒤 읽는가"를 본다. 안쪽 루프의 몸체는 제 범위를 갖는다. 그래서 안쪽
+ 회차를 넘어 이어지는 값(roots의 `r`, matmul의 `s`)은 레인의 상태가 되고, 바깥 회차를 넘는 값은 모으기여야 한다.
+- **레인 재사용.** 안쪽 회차가 끝날 때마다, 지역 변수와 모으기와 마스크와 중첩 상태가 쥐지 않은 레인 버퍼와 마스크를 다시 빈 목록에
+ 넣는다(쓸어 담기). 그래서 회차가 64번이어도 레인 버퍼 24개 안에서 돈다.
+
+## 다듬기: 측정이 가리킨 것
+
+2단계를 처음 만들었을 때 roots는 16.8 ms였다. 프로파일에서 가장 큰 것은 유한 검사였다(17%). 벡터화는 돼 있었지만 산수와 따로
+한 번 더 도는 루프였다. 검사를 산수 루프 안에서 지수 비트로 하게 했다. 둘째는 수 비교가 8바이트 0/1을 내서, 분기가 레인마다 값의
+꼴을 물어 다시 읽던 것이었다(8%). 비교가 4바이트 0/1 레인을 내게 해서 그대로 마스크가 되게 했다. 셋째는 바깥 루프의 값(아핀)을 안쪽
+회차마다 수 레인으로 다시 만들던 것이었다(7.6%). 묶음마다 한 번만, 그것도 필요할 때만 만들게 했다. 처음에는 묶음마다 미리 만들게
+해서 knapsack이 13.8에서 17.9 ms로 느려졌다(그 루프는 필요 없다). 그 밖에 아무 레인도 빠지지 않은 동안은 살아 있는 레인의 마스크를
+만들지 않고, 작은 정수를 세는 모으기는 2^53 검사를 건너뛴다. roots 16.8 → 9.1 ms, matmul 68 → 50 ms.
+
+## 뜻과 맞음
+
+- 생성기에 `nest` 꼴을 더했다. 바깥 루프 안에 회차 수가 레인마다 다른 안쪽 루프가 있고, 그 안에 `break`, 회차를 넘는 지역 변수,
+ 분수가 섞인 안팎의 모으기(`ra`, `rb`, `rc`)가 있다. 시드 1, 2, 3, 5, 23의 프로그램 685개가 세 길(묶음, 실행기만, 기계어)에서 같았다.
+ 시드 23에서는 137개 중 53개 모듈에서 묶음 4만 1,554개(419만 회차)가 돌았다. 그중 4만 142개가 중첩 루프를, 4만 944개가 모으기를
+ 품었다.
+- `NESTED_SOURCE`: Newton 걸음과 `break`, 회차마다 분수 하나의 합, 레인마다 0–6회차인 안쪽 루프에서 어떤 레인의 회차에만 나는 0
+ 나누기, `max`와 `min`, 레인마다 분수 여럿의 합(묶음이 돌려준다), −0의 합, 격자의 합을 담았다. 작은 예산 둘도 돈다.
+- 단위 시험(실행기 모드)이 통과했다.
+
+## 다음
+
+- **matmul.** 안쪽 회차마다 해석하는 명령을 줄인다. 하나는 회차에 따라 바뀌지 않는 계산(`i * n`)을 루프 밖으로 끌어내는 것이고,
+ 하나는 합친 연산이다. "읽어서 곱해 더하기"는 한 연산이 될 수 있다.
+- **점화식(5단계)과 덧붙이기(4단계).** matmul, knapsack, join의 표를 만드는 루프가 여기에 걸린다. 지금은 실행기에서 돈다.
+- **join과 BFS.** 키로 모으기와 차례대로 흩어 더하기(3단계), `EACH`와 덧붙이기(4단계)가 필요하다.