diff options
| author | eunhhu <[email protected]> | 2026-10-05 21:41:34 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-05 21:41:34 +0100 |
| commit | af43fa227f71c16eb806191d4928cf03166ff76c (patch) | |
| tree | dad07cfc86ed70f7a4245a8f1da375b1d65ed856 /docs/design | |
| parent | bbed2c103f839af7ede4b3d86b22b15138480372 (diff) | |
| download | pure-af43fa227f71c16eb806191d4928cf03166ff76c.tar.gz pure-af43fa227f71c16eb806191d4928cf03166ff76c.zip | |
024: the table with operations apart and interpreted beside the hand-fused forms (geometric mean 1.58 and 1.24, the tier 1.92), the prototypes as a ceiling (124's floor 0.90x became 1.2-2.5x), division a third of the interpreted recurrence, the distance rule for equal strides only, why not copy-and-patch, matmul's miss without a fused multiply-add; CHECKPOINT says so
Diffstat (limited to 'docs/design')
| -rw-r--r-- | docs/design/024-batch-executor.md | 70 |
1 files changed, 36 insertions, 34 deletions
diff --git a/docs/design/024-batch-executor.md b/docs/design/024-batch-executor.md index 233241c..1e6fae6 100644 --- a/docs/design/024-batch-executor.md +++ b/docs/design/024-batch-executor.md @@ -14,35 +14,36 @@ C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-s ## 산수: 시제품 (041) -모두 Pi 5(Cortex-A76 2.4 GHz), 조용한 기기, 한 프로세스에서 번갈아 11번 돈 중앙값을 세 번(`2026-10-05-batches.jsonl`)이다. 답은 -매번 C와 같다. 견주는 C는 두 가지다. 하나는 `natural.h`의 C를 크기가 상수가 아닌 채로 돈 것이고(칸은 미리 만든다), 다른 하나는 -묶음이 받은 재구성(루프 바꾸기, 4바이트 칸, 벡터화)을 C에도 준 것이다. 계층은 `e01dae9`의 `suite-run`이다(C는 칸 만들기를 포함). +모두 Pi 5(Cortex-A76 2.4 GHz), 조용한 기기에서 쟀다. 한 프로세스에서 번갈아 11번 돈 중앙값을 세 번 냈고, 표는 그 셋의 중앙값이다 +(`2026-10-05-batches.jsonl`). 답은 매번 C와 같았다. 견주는 C는 두 가지다. 하나는 `natural.h`의 C를 크기가 상수가 아닌 채로 돈 +것이다(칸은 미리 만든다). 다른 하나는 묶음이 받은 재구성(루프 바꾸기, 4바이트 칸, 벡터화)을 C에도 준 것이다. 계층의 값은 `e01dae9`의 +`suite-run`에서 왔다(그 C는 칸 만들기를 포함한다). -| 일 | C | 재구성을 받은 C | 묶음 | 묶음 / C | 계층 / C | +| 일 | C | 재구성을 받은 C | 연산 따로, 해석 | 합친 꼴 (손으로) | 계층 / C | |---|---:|---:|---:|---:|---:| -| roots | 6.86 ms | — | 6.79 | **0.99** | 1.39 | -| knapsack, 연산 따로 (4바이트 레인) | 6.38 | 1.97 (묶음의 블록, −O3) | 4.54 | **0.71** | 1.20 | -| knapsack, 합친 연산 | | | 2.71 | 0.42 | | -| matmul, 합친 연산 (j 레인) | 7.09 | 4.89 (j·k 바꿈, 4바이트, −O3) | 6.68 | **0.94** | 1.90 | -| matmul, 연산 따로 (j 레인 / k 레인) | | | 17.7 / 22.1 | 2.50 / 3.11 | | -| join, 점화식을 C로 | 28.8 | — | 36.5 | 1.27 | 2.46 | -| join, 점화식을 해석 (역수 준비) | | | 45.7 | **1.59** | | -| BFS | 9.97 | 7.02 (미리 읽기) | 10.28 | **1.03** | 1.61 | - -- 굵은 값(배낭은 연산 따로, 행렬 곱은 합친 연산)에 sort 1.37과 words 5.58을 그대로 두면 기하 평균이 **1.35**다. 계층은 1.92다. - 배낭도 합치면 1.26이고, 합친 연산이 하나도 없으면(matmul 2.50) 1.56이다. 재구성을 받은 C와 견주면 1.65–1.77이다. words를 - 빼면(보류) 1.07이다. -- 023의 기준(일마다 1.25배 안)을 넘는 것은 join(1.59), sort(1.37), words(5.58)다. sort는 런타임의 기수 정렬이고 words는 보류라, - 묶음의 몫이 아니다. join의 남은 몫은 아래 "회차마다 도는 부분"에 있다. -- 배낭의 0.71과 0.42는 C를 이긴 것이 아니다. gcc가 자연스러운 배낭 루프를 벡터로 만들지 못한 것이다(거꾸로 가는 루프, −O3와 - ivdep를 줘도 9.5 ms). 같은 블록으로 쓴 C는 1.97 ms다. 묶음이 이기는 것은 실행 시간에 확인한 독립(아래)이다. -- 행렬 곱의 C는 하네스마다 다르다. `suite-run`은 7.44 ms이고(칸 만들기 포함, n은 실행 시간), 처음의 시제품은 n=200이 상수로 +| roots | 6.87 ms | — | 6.79 (0.99)¹ | | 1.39 | +| knapsack | 6.37 | 1.96 (묶음의 블록, −O3) | 4.55 (0.71) | 2.70 (0.42) | 1.20 | +| matmul (j 레인) | 7.11 | 4.90 (j·k 바꿈, 4바이트, −O3) | 17.75 (2.50) | 6.68 (0.94) | 1.90 | +| join | 32.6 | — | 57.8 (1.77)² | 48.4 (1.49)³ | 2.46 | +| BFS | 9.97 | 7.04 (미리 읽기) | 10.30 (1.03)¹ | | 1.61 | + +¹ 디스패치 없이 연산마다 레인을 한 번 도는 꼴이다. roots에 디스패치를 더해도 묶음 391개 × 걸음 64 × 연산 4로 10만 번 아래다. +² 점화식은 레인마다 해석하고(역수 준비), 회차의 나머지는 연산 11개를 하나씩 해석한다. ³ 회차의 나머지를 한 줄씩의 C 루프로 +합쳤다. 점화식까지 C로 하면 39.9 ms(1.22)다. + +- 일곱 가지 일의 기하 평균은 계층이 1.92다. sort 1.37과 words 5.58은 그대로 두었다. 연산 따로로 하면 **1.58**이고, 합친 꼴로 하면 + **1.24**다. words를 빼면(보류) 1.28과 0.97이다. 재구성을 받은 C와 견주면 2.07과 1.63이다. +- 배낭의 0.71과 0.42는 C를 이긴 것이 아니다. gcc가 자연스러운 배낭 루프를 벡터로 만들지 못한 것이다(거꾸로 가는 루프라, −O3와 + ivdep를 줘도 9.5 ms였다). 같은 블록으로 쓴 C는 1.96 ms다. 묶음이 앞서는 것은 실행 시간에 확인한 독립(아래) 덕이다. +- 행렬 곱의 C는 하네스마다 다르다. `suite-run`에서는 7.44 ms다(칸 만들기 포함, n은 실행 시간). 처음의 시제품은 n=200이 상수로 인라인돼 4.9 ms였다. 위 표는 n을 실행 시간에 읽는다. +- 같은 일에서 연산을 따로 두는 값: 배낭은 4바이트 레인으로 1.7배, 8바이트 레인과 거꾸로 가는 적재로는 4.2배(11.2 ms)다. 행렬 + 곱은 2.7배다(k 레인으로는 22.1 ms). join은 1.19배다. -**시제품은 천장이다.** 묶음 프로그램은 손으로 짰다(컴파일러가 낼 순서). 작업량을 세지 않고, 적재의 꼴 검사(열의 폭, DENSE의 -빈틈)를 하지 않으며, 실패 뒤 다시 도는 길을 돌려 보지 않았다. roots는 해석기의 디스패치 없이 연산마다 살아 있는 레인을 도는 -루프다. 디스패치를 더해도 묶음 391개 × 걸음 64 × 연산 4로 10만 번 아래다. knapsack의 두 묶음과 matmul의 k·j 레인은 해석기가 -연산마다 디스패치한다. +**시제품은 천장이다.** 묶음 프로그램은 손으로 짰다(컴파일러가 낼 순서대로). 작업량을 세지 않았고, 적재의 꼴 검사(열의 폭, DENSE의 +빈틈)도 하지 않았다. 실패 뒤 다시 도는 길도 돌려 보지 않았다. 지난번에 손으로 쓴 바닥(124)은 C의 0.90배였지만, 계층은 일마다 +1.2–2.5배(텍스트 5.6배)에 닿았다. 디스패치를 넣고 잰 것은 knapsack(4.55, 11.2), matmul(17.75, 22.1), join(57.8)이다. 합친 +꼴의 값은 합친 연산의 표가 그 꼴을 덮을 때만 나온다. ## 왜 되는가 @@ -50,7 +51,7 @@ C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-s 2. **검사가 분기가 아니라 깃발이다.** `forms.c`에서 적재마다 앞에 둔 분기(한 번도 타지 않아도)가 1.34배였다. 2^53 검사는 거의 공짜였다. 묶음에서는 검사가 OR로 깃발에 모이고, 적재 앞에 분기가 없다. 3. **레인이 좁고 메모리의 차례다.** Pure의 열은 4바이트다. 4바이트 레인은 NEON에 넷이 들어간다. 배낭을 8바이트 레인과 거꾸로 가는 - 적재로 하면 11.2 ms, 4바이트 레인과 메모리 차례로 하면 4.54 ms다. 묶음 안의 회차가 서로 독립이면 레인의 차례는 자유다. + 적재로 하면 11.2 ms, 4바이트 레인과 메모리 차례로 하면 4.55 ms다. 묶음 안의 회차가 서로 독립이면 레인의 차례는 자유다. 4. **회차 사이의 병렬.** roots는 i마다 나눗셈의 사슬이다. C는 사슬 하나씩 돌아 나눗셈의 지연을 기다린다. 묶음은 사슬 256개를 섞어 지연을 겹친다. 같은 산수, i마다 같은 차례, 같은 검사다. 5. **메모리 미스가 겹친다.** join의 탐사와 BFS의 비트 읽기는 레인마다 독립이라 미스가 함께 나간다. @@ -66,7 +67,7 @@ C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-s - **아핀 키의 읽기와 쓰기.** 같은 열을 레인 l이 `r0 + s·l`에서 읽고 `w0 + s·l`에 쓰면, 앞선 레인 l' < l의 쓰기를 읽는 것은 `l' = l + (r0 − w0)/s`일 때다. 그래서 거리 `d = (r0 − w0)/s`가 0 이상이거나 정수가 아니면 묶음 안에서 안전하다. 거리가 음수면 묶음의 크기를 |d| 아래로 한다. 배낭(용량을 거꾸로, `s = −1`, `r0 − w0 = −w`)은 거리가 무게 w ≥ 0이라 안전하다. 같은 배낭을 앞으로 - 돌면(무한 배낭) 거리가 −w여서 묶음이 w 회차로 줄어든다. 이 판정은 023의 BCE처럼 묶음을 열 때 실행 시간의 값으로 한다. + 돌면(무한 배낭) 거리가 −w여서 묶음이 w 회차로 줄어든다. 이 판정은 023의 BCE처럼 묶음을 열 때 실행 시간의 값으로 한다. 이 규칙은 읽기와 쓰기의 보폭이 같을 때만이다. 보폭이 다르면(예: `2l`에서 읽고 `l`에 쓰기) 일반의 판정은 열린 문제여서, 그런 루프는 처음에는 묶지 않는다. - **커지기만 하는 걸러내기.** 루프가 리스트에 더하기만 하면(빼지 않으면) `has`는 한 번 참이면 계속 참이다. 그래서 묶음 전체에 `has`를 먼저 묻고, 거짓인 레인만 차례대로 다시 물어 더하고 덧붙인다(BFS). - **바뀌는 개수.** `if i >= queue.count { break }`처럼 회차의 시험이 커지기만 하는 값에 달려 있으면, 묶음을 열 때 시험이 서는 회차까지만 @@ -79,9 +80,9 @@ C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-s ### 회차마다 도는 부분 `x = x * 48271 mod (2^31 − 1)`처럼 회차가 앞 회차의 값을 받는 점화식은 묶을 수 없다. 이 부분은 같은 연산 집합을 레인마다 차례로 -해석한다(계산된 goto). 결과는 레인마다 하나씩 남고, 나머지 몸체는 묶음으로 돈다. join에서 이 부분이 회차 110만에 9–14 ms였다. -그 대부분은 상수로 나누는 정수 나눗셈의 지연이다. 프로그램을 만들 때 상수의 역수를 준비하면 4 ms를 던다. 이 부분이 C에 남은 -차이의 대부분이다(36.5 대 45.7). 기계어 없이 더 줄이려면, 자주 나오는 점화식의 꼴을 합친 연산으로 둔다(아래). +해석한다(계산된 goto). 결과는 레인마다 하나씩 남고, 나머지 몸체는 묶음으로 돈다. join에서 이 부분이 회차 110만에 약 13 ms였다 +(52.7 대 39.9). 그 3분의 1은 상수로 나누는 정수 나눗셈의 지연이다. 프로그램을 만들 때 상수의 역수를 준비하면 4 ms를 던다(48.4). +나머지는 디스패치와 연산 다섯의 사슬이다. 기계어 없이 더 줄이려면, 자주 나오는 점화식의 꼴을 합친 연산으로 둔다(아래). ### 연산 @@ -89,7 +90,7 @@ C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-s - **연산의 종류**: 검사하는 원소별 산수, 비교와 마스크, 고르기, 아핀 적재와 저장(보폭 ±1, k), 키로 모으기(DENSE는 키 자리, HASHED는 탐사, FLAT은 위치), 차례대로 흩어 더하기, 모으기, 살아 있는 레인 모으기와 펼치기. - **합친 연산**은 빌드 때 스크립트가 꼴의 표에서 C 루프로 만든다(곱해 더하기, 더하고 최대로 저장, 읽고 비교해 고르기 같은 것). - 묶음 컴파일러는 꼴이 맞으면 합친 연산을 쓰고, 아니면 따로 둔다. 재어 보니 합치면 배낭이 1.7배, 행렬 곱이 2.7배 빨랐다. 연산을 + 묶음 컴파일러는 꼴이 맞으면 합친 연산을 쓰고, 아니면 따로 둔다. 재어 보니 합치면 배낭이 1.7배, 행렬 곱이 2.7배, join이 1.19배 빨랐다. 연산을 따로 두면 레인 배열(L1 안)을 연산마다 읽고 쓰기 때문이다. 기계어를 만드는 것과 달리 표는 유한하다. 그래서 어떤 꼴을 둘지는 묶음과 앱에서 나오는 프로그램을 세어 정한다. @@ -109,7 +110,7 @@ C의 3–300배에서 1.92배로 줄였지만([125](../research/125-tier-first-s ### JIT와의 관계 -022의 JIT(copy 0/1)와 023의 계층은 기계어를 쓴다. 묶음 실행기는 C 컴파일러가 가는 곳 어디서나 같은 코드로 돈다. Mac과 WSL의 +022의 JIT(copy 0/1)와 023의 계층은 기계어를 쓴다. 묶음 실행기는 C 컴파일러가 가는 곳 어디서나 같은 코드로 돈다. copy-and-patch도 빌드 때 C로 만든 조각을 쓰지만, 실행 시간에 그 조각을 복사하고 고쳐 실행 가능한 페이지에 쓰고, 기계마다 조각과 고치기가 따로다. 묶음 실행기는 빌드 때 만든 C를 쓰는 생각은 같고, 코드를 쓰지 않는다. Mac과 WSL의 x86에서도 연산은 그 기계의 벡터로 빌드된다. 023의 앞 절반(SSA, 사실, 아핀 키, 루프를 열 때의 검사)은 그대로 쓰고, 뒤 절반(레지스터 할당과 기계어 내기)은 쓰지 않는다. 묶음 실행기가 묶음의 일과 앱에서 계층보다 느리지 않으면 JIT를 걷어낸다(022의 장부, 023의 내기). 느린 곳이 남으면 그곳과 값을 적고 사용자에게 묻는다. @@ -132,5 +133,6 @@ x86에서도 연산은 그 기계의 벡터로 빌드된다. 023의 앞 절반(S - 무작위 시험에서 답과 작업량이 회차마다 돈 것과 같다(작은 예산과 묶음 한가운데의 실패를 섞어). - 이벤트 스레드 하나의 레인 자리가 64 KB 안이다. - TPC-C와 은행 질의가 잡음 안에서 그대로다. -- **미리 밝히는 미달**: 시제품으로 보면 join은 점화식을 해석하는 한 1.25배를 넘는다(1.59). sort(런타임의 정렬)와 words(보류)는 이 - 설계로 바뀌지 않는다. 합친 연산의 표가 꼴을 덮지 못하면 행렬 곱 같은 일은 2.5배로 남는다. +- **미리 밝히는 미달**: 시제품으로 보면 join은 점화식을 해석하는 한 1.25배를 넘는다(연산 따로 1.77, 합친 꼴 1.49). 계층(2.46)보다는 + 빠르다. sort(런타임의 정렬)와 words(보류)는 이 설계로 바뀌지 않는다. 합친 연산의 표가 꼴을 덮지 못하면 행렬 곱은 2.5배로 + 남는다(계층 1.90보다 느리다). 그래서 행렬 곱에는 합친 연산(곱해 더하기)이 첫 표에 있어야 한다. |
