From fffe01904984aebd10b237e400859b5ff86f0729 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 5 Oct 2026 23:22:55 +0100 Subject: 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 --- docs/research/127-batch-executor-stage-two.md | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/research/127-batch-executor-stage-two.md (limited to 'docs/research') 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단계)가 필요하다. -- cgit v1.2.3