diff options
| author | eunhhu <[email protected]> | 2026-10-08 11:47:34 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-08 11:47:34 +0100 |
| commit | ee4e1510819a916bf84cd4b560b05f520d32e86b (patch) | |
| tree | 50f3702b07f990579f385164f2b6e0b820e77a92 | |
| parent | 6b6fbc29d809911ba8313e74eb429195f900f52a (diff) | |
| download | pure-ee4e1510819a916bf84cd4b560b05f520d32e86b.tar.gz pure-ee4e1510819a916bf84cd4b560b05f520d32e86b.zip | |
Research 131: the batch executor's general cuts, by the user's choice of the general way over a table of fused operations: values computed by the same code taken from the first, if !c one branch, selections (an arm's few lanes alone), keys as bits asked in one loop, L.first + i affine, a slice's bounds at once: the suite's BFS 211 to 112 instructions an edge, 41 to 24 ms (the compiled code 38.5, 7.7); where the rest goes and the floor of the general way (about twice the compiled code); knapsack and matmul 10% slower, cause not fully told; 024 and CHECKPOINT say so
| -rw-r--r-- | CHECKPOINT.md | 2 | ||||
| -rw-r--r-- | docs/design/024-batch-executor.md | 2 | ||||
| -rw-r--r-- | docs/research/131-batch-executor-general-cuts.md | 95 |
3 files changed, 97 insertions, 2 deletions
diff --git a/CHECKPOINT.md b/CHECKPOINT.md index 7dc06be..6d3759a 100644 --- a/CHECKPOINT.md +++ b/CHECKPOINT.md @@ -13,7 +13,7 @@ | 영역 | 확인한 것 | 근거 | |---|---|---| | 앱 표현 | 상태(수, `bool`, 정확한 64비트 정수 `i64`, text와 커서·마크, 레코드의 리스트와 키 있는 리스트, 인덱스), 입력(키, text, 필드 있는 메시지, 타이머), 원자적 이벤트 핸들러, 순수 보기(조건식, 표와 걸러진 표, count·sum·max·min·first), `fn`·`use` 라이브러리, text 비교, 시간(`now`, `every`, `floor`, `%`). 로드할 때 타입·스택·합류·커서와 인덱스의 소속을 모두 검증하고 이벤트의 실행량에 상한이 있다 | 008, [016](docs/design/016-bool-and-i64.md), aup `docs/design/002-dsl-direction.md`, aup `make check`(파이썬 참조 모델 대조, 진단 223건) | -| 수명과 계산 | 값의 수명은 선언이 말한다: `state`(저장), `transient state`(앱이 도는 동안: 저장소에 쓰지 않고 다시 시작하면 초기값, SD 카드에서 쓰기 17배), `let`(이벤트 동안). 핸들러와 보기가 계산한다: 이벤트의 리스트(`let seen: list of Seen by node`), 세는 `for i in a .. b`, `break`, 8겹까지의 중첩, 그리고 `view 이름(인자) { … return … }`(스냅숏에서 돌아 소유자를 막지 않는다). 모든 반복은 시작할 때 횟수의 상한을 갖고, 모든 이벤트와 보기는 실행량 예산(정하지 않으면 2^28 명령)과 메모리 몫(2^28 바이트) 안에서 끝난다. 노드 10만·간선 100만의 BFS 한 이벤트가 HTTP로 90 ms였고(처음 397, 020 뒤 191; CPython 452, C 9.1, 123), 022 뒤 코어는 7.4 ms다(같은 프로세스의 C 9.9): 많이 찾는 저장된 리스트를 배열과 칸의 색인으로 펴고, 열을 값만큼 좁히고, 루프 묶음을 레지스터의 기계어로 돈다(리눅스 AArch64의 JIT; 다른 곳은 실행기, 답은 같다). 다만 C를 넘은 것은 미리 읽기 덕이고, 끄면 15.7 ms로 C의 1.57배다. 일곱 가지 일(실수 루프, 배낭, 정렬, 조인, 텍스트, 행렬, BFS)로 C와 견주면 124에서는 3–300배 느렸고, 023의 계층과 표현(HASHED: 차례를 어긴 키는 해시 표의 칸에, 위치가 필요할 때 정렬)으로 지금은 1.19–5.58배(기하 평균 1.92)다(125). Pure의 표현과 뜻대로 손으로 쓴 바닥은 C의 0.90배이고 뜻의 검사는 끌어내면 0–3%라, 막는 것은 실행 계층이다(124). 모든 블록을 SSA로 올리는 최적화 계층(023)의 첫 걸음들이 들어갔고, 텍스트(실행기로 돈다)와 BFS(계층이 덧붙이기를 못 한다)가 남았다. 기계어를 만들지 않는 길로, 루프를 회차 256개의 묶음으로 돌고 연산마다 빌드 때 만든 C 루프를 쓰는 묶음 실행기(024)를 시제품으로 재니 연산을 따로 해석하면 roots 0.99배, 배낭 0.71배(4바이트 레인), BFS 1.03배, join 1.77배, 행렬 곱 2.50배이고, 행렬 곱은 곱해 더하기를 합치면 0.94배였다(계층 1.20–2.46배, 기하 평균 1.92 대 1.58, 합친 꼴로 1.24; 041, 손으로 짠 천장). 1단계(세는 루프의 레인 꼴 몸체, 회차 1024개씩)가 들어가 knapsack이 실행기에서 1,041 ms에서 13.6 ms가 됐다(계층 7.5, 안쪽 루프는 1.17배). 묶음 루프가 없는 모듈의 실행기는 명령과 사이클이 그대로다(126). 2단계(중첩 루프를 레인마다 함께, `x = x + e`·`min`·`max` 모으기)로 roots가 9.13 ms가 되어 기계어 없이 계층(9.57)보다 빠르고, matmul은 914.9에서 50.0 ms다(계층 14.3, 127). 덧붙이기, 점화식, 칸에 더하기, 걷기를 묶어 join이 실행기의 423에서 161 ms가 됐다(계층 74). 남은 값은 덧붙이기 하나 약 100 ns(실행기와 같다), join의 점화식 회차당 약 60 ns, 레인이 적은 중첩 몸체의 해석이고, BFS는 묶이지 않아 기계어를 아직 걷어낼 수 없다(128). 덧붙이기를 리스트의 한 호출로, 점화식을 레지스터 꼴로, 모으기의 끝을 차례 없이 하자 knapsack 10.8, matmul 36.4, join 118, sort 55.5 ms가 됐다(기계어 7.9, 14.2, 72–79, 35.6; 129). BFS는 프런티어 묶음으로 셈하면 차례대로 할 꼬리가 간선의 약 10%다(129). 가드 루프, 묶음 안의 걷기, 커지기만 하는 걸러내기로 BFS도 묶음으로 돌아 93에서 41 ms가 됐다(기계어 7.7, C 10). 간선당 명령 211개 대 38.5개이고, 이 꼴의 바닥(약 50개)이 기계어 위라서 기계어를 걷어내려면 합친 연산이 필요하다(130). TPC-C −2.8%(기준 3% 안), 은행 질의의 명령 수는 1% 안. 같은 계산이 Pi(JIT)·WSL2·M3 Mac에서 같다. 같은 탐색을 보기로 하면 쓰기는 CPU를 나누는 만큼만 줄고(이벤트로 하면 0.2%로 멈춘다), 본문이 있는 보기는 보기의 스레드(`--view-workers`)가 계산해 연결 스레드를 막지 않는다(쓰기 최대 지연이 CPU만 나눌 때의 1.0배, 연결 스레드 하나에서 묻는 연결 둘이 1.95배) | [018](docs/design/018-lifetimes-and-computation.md), [020](docs/design/020-computing-faster.md), [117](docs/research/117-transient-states.md), [118](docs/research/118-computing-in-one-event.md), [119](docs/research/119-views-that-compute.md), [121](docs/research/121-computing-faster.md), [122](docs/research/122-views-off-the-connection-threads.md), [021](docs/design/021-past-c.md), [123](docs/research/123-past-c.md), [022](docs/design/022-arrays-and-registers.md), [124](docs/research/124-against-c.md), [023](docs/design/023-optimizing-tier.md), [125](docs/research/125-tier-first-steps.md), [024](docs/design/024-batch-executor.md), [126](docs/research/126-batch-executor-stage-one.md), [127](docs/research/127-batch-executor-stage-two.md), [128](docs/research/128-batch-executor-tables-and-costs.md), [129](docs/research/129-batch-executor-costs-cut.md), [130](docs/research/130-batch-executor-frontiers.md) | +| 수명과 계산 | 값의 수명은 선언이 말한다: `state`(저장), `transient state`(앱이 도는 동안: 저장소에 쓰지 않고 다시 시작하면 초기값, SD 카드에서 쓰기 17배), `let`(이벤트 동안). 핸들러와 보기가 계산한다: 이벤트의 리스트(`let seen: list of Seen by node`), 세는 `for i in a .. b`, `break`, 8겹까지의 중첩, 그리고 `view 이름(인자) { … return … }`(스냅숏에서 돌아 소유자를 막지 않는다). 모든 반복은 시작할 때 횟수의 상한을 갖고, 모든 이벤트와 보기는 실행량 예산(정하지 않으면 2^28 명령)과 메모리 몫(2^28 바이트) 안에서 끝난다. 노드 10만·간선 100만의 BFS 한 이벤트가 HTTP로 90 ms였고(처음 397, 020 뒤 191; CPython 452, C 9.1, 123), 022 뒤 코어는 7.4 ms다(같은 프로세스의 C 9.9): 많이 찾는 저장된 리스트를 배열과 칸의 색인으로 펴고, 열을 값만큼 좁히고, 루프 묶음을 레지스터의 기계어로 돈다(리눅스 AArch64의 JIT; 다른 곳은 실행기, 답은 같다). 다만 C를 넘은 것은 미리 읽기 덕이고, 끄면 15.7 ms로 C의 1.57배다. 일곱 가지 일(실수 루프, 배낭, 정렬, 조인, 텍스트, 행렬, BFS)로 C와 견주면 124에서는 3–300배 느렸고, 023의 계층과 표현(HASHED: 차례를 어긴 키는 해시 표의 칸에, 위치가 필요할 때 정렬)으로 지금은 1.19–5.58배(기하 평균 1.92)다(125). Pure의 표현과 뜻대로 손으로 쓴 바닥은 C의 0.90배이고 뜻의 검사는 끌어내면 0–3%라, 막는 것은 실행 계층이다(124). 모든 블록을 SSA로 올리는 최적화 계층(023)의 첫 걸음들이 들어갔고, 텍스트(실행기로 돈다)와 BFS(계층이 덧붙이기를 못 한다)가 남았다. 기계어를 만들지 않는 길로, 루프를 회차 256개의 묶음으로 돌고 연산마다 빌드 때 만든 C 루프를 쓰는 묶음 실행기(024)를 시제품으로 재니 연산을 따로 해석하면 roots 0.99배, 배낭 0.71배(4바이트 레인), BFS 1.03배, join 1.77배, 행렬 곱 2.50배이고, 행렬 곱은 곱해 더하기를 합치면 0.94배였다(계층 1.20–2.46배, 기하 평균 1.92 대 1.58, 합친 꼴로 1.24; 041, 손으로 짠 천장). 1단계(세는 루프의 레인 꼴 몸체, 회차 1024개씩)가 들어가 knapsack이 실행기에서 1,041 ms에서 13.6 ms가 됐다(계층 7.5, 안쪽 루프는 1.17배). 묶음 루프가 없는 모듈의 실행기는 명령과 사이클이 그대로다(126). 2단계(중첩 루프를 레인마다 함께, `x = x + e`·`min`·`max` 모으기)로 roots가 9.13 ms가 되어 기계어 없이 계층(9.57)보다 빠르고, matmul은 914.9에서 50.0 ms다(계층 14.3, 127). 덧붙이기, 점화식, 칸에 더하기, 걷기를 묶어 join이 실행기의 423에서 161 ms가 됐다(계층 74). 남은 값은 덧붙이기 하나 약 100 ns(실행기와 같다), join의 점화식 회차당 약 60 ns, 레인이 적은 중첩 몸체의 해석이고, BFS는 묶이지 않아 기계어를 아직 걷어낼 수 없다(128). 덧붙이기를 리스트의 한 호출로, 점화식을 레지스터 꼴로, 모으기의 끝을 차례 없이 하자 knapsack 10.8, matmul 36.4, join 118, sort 55.5 ms가 됐다(기계어 7.9, 14.2, 72–79, 35.6; 129). BFS는 프런티어 묶음으로 셈하면 차례대로 할 꼬리가 간선의 약 10%다(129). 가드 루프, 묶음 안의 걷기, 커지기만 하는 걸러내기로 BFS도 묶음으로 돌아 93에서 41 ms가 됐다(기계어 7.7, C 10). 간선당 명령 211개 대 38.5개이고, 이 꼴의 바닥(약 50개)이 기계어 위라서 기계어를 걷어내려면 합친 연산이 필요하다(130). 범용 장치만으로(같은 코드의 값 다시 쓰기, 고르기, 비트로 묻기, 아핀 `first + i`) 간선당 112개, 24.0 ms가 됐고, 범용의 바닥은 기계어의 약 2배로 보인다(131). TPC-C −2.8%(기준 3% 안), 은행 질의의 명령 수는 1% 안. 같은 계산이 Pi(JIT)·WSL2·M3 Mac에서 같다. 같은 탐색을 보기로 하면 쓰기는 CPU를 나누는 만큼만 줄고(이벤트로 하면 0.2%로 멈춘다), 본문이 있는 보기는 보기의 스레드(`--view-workers`)가 계산해 연결 스레드를 막지 않는다(쓰기 최대 지연이 CPU만 나눌 때의 1.0배, 연결 스레드 하나에서 묻는 연결 둘이 1.95배) | [018](docs/design/018-lifetimes-and-computation.md), [020](docs/design/020-computing-faster.md), [117](docs/research/117-transient-states.md), [118](docs/research/118-computing-in-one-event.md), [119](docs/research/119-views-that-compute.md), [121](docs/research/121-computing-faster.md), [122](docs/research/122-views-off-the-connection-threads.md), [021](docs/design/021-past-c.md), [123](docs/research/123-past-c.md), [022](docs/design/022-arrays-and-registers.md), [124](docs/research/124-against-c.md), [023](docs/design/023-optimizing-tier.md), [125](docs/research/125-tier-first-steps.md), [024](docs/design/024-batch-executor.md), [126](docs/research/126-batch-executor-stage-one.md), [127](docs/research/127-batch-executor-stage-two.md), [128](docs/research/128-batch-executor-tables-and-costs.md), [129](docs/research/129-batch-executor-costs-cut.md), [130](docs/research/130-batch-executor-frontiers.md), [131](docs/research/131-batch-executor-general-cuts.md) | | 보기 밀기 | 보기의 `GET`에 `Accept: text/event-stream`이면 그 값을 민다(SSE, 브라우저의 `EventSource` 그대로). 같은 보기와 인자를 보는 구독자는 계산 한 번을 나누고, 값이 바뀔 때만, 많아야 10 ms마다 한 번 보낸다. 느린 구독자에게는 가장 새 값 하나만 기다리게 한다. 모듈이 바뀌어도 같은 이름의 보기로 이어 가고, 토큰을 빼면 끊는다. 구독자 1,000에 쓰기 초당 100번까지 신선도 p99 11 ms 이하, 초당 1번이면 100 ms 폴링의 약 10분의 1 CPU(이벤트 하나와 요청 하나의 값이 같아 메시지 수의 비다), 구독자 1만에 구독자마다 0.3–0.4 KiB, 1만에게 퍼지기까지 p99 45 ms | [019](docs/design/019-pushing-views.md), [120](docs/research/120-pushing-views.md) | | 영속 | 선언한 상태가 곧 영속 상태다. 목록은 이미지와 로그(저장마다 바뀐 기록 한 항목, 재사용하는 할당 구간, 앞 항목의 다이제스트로 이어짐)에, 나머지는 트리 저장소에(커밋당 한 연속 구간과 동기화 한 번). 이벤트 묶음마다 저장 한 번, 이미지는 소유자 밖에서 체크포인트. `kill -9`와 커밋 각 단계의 죽음 뒤 확인 응답한 이벤트가 모두 남는다. 레코드·키가 바뀐 모듈로 바꿔도 데이터가 이름으로 옮겨진다 | [076](docs/research/076-one-sync-commit.md), [081](docs/research/081-tail-records.md), [092](docs/research/092-tpcc.md), 008 상태 영속·교체 | | 인터페이스 | 한 명세가 TUI(셀 단위 갱신), CLI, 문장(NUI), 웹 GUI가 된다. 저장하면 실행 중인 앱이 상태를 이어 새 모듈로 바뀐다(`aup watch`, `vega … --watch`) | Vega `docs/design/001-first-component.md`, Vega `make check`(가상 터미널, 헤드리스 Chromium 포함) | diff --git a/docs/design/024-batch-executor.md b/docs/design/024-batch-executor.md index ec9a6c8..2ed21f5 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배로 기준 미달). 2단계(중첩 루프를 함께, 모으기)를 만들었다([127](../research/127-batch-executor-stage-two.md): roots 9.13 ms로 계층 9.57보다 빠름, matmul 50 ms). 4단계의 앞 절반(덧붙이기), 5단계(점화식), 칸에 더하기, 걷기를 만들었다([128](../research/128-batch-executor-tables-and-costs.md): join이 실행기의 423에서 161 ms, 계층 74). 남은 값은 덧붙이기 하나 약 100 ns, join의 점화식 회차당 약 60 ns, 레인이 적은 중첩 몸체의 해석이고, BFS는 아직 묶이지 않는다(바뀌는 개수, 커지기만 하는 걸러내기, 중첩 걷기가 필요하다). 그 셋을 줄였다([129](../research/129-batch-executor-costs-cut.md): 한꺼번에 덧붙이기, 점화식을 레지스터 꼴로, 차례 없는 모으기; knapsack 10.8, matmul 36.4, join 118 ms, roots 8.87로 계층 9.57보다 빠름). BFS를 셈하니 차례대로 할 꼬리가 간선의 약 10%라, 꼬리를 묶음의 C로 하고 간선을 레인으로 펼치면 계층과 겨룰 만하다(약 6–7 ms 예상). 가드, 묶음 안의 걷기(함께, 펼치기), 걸러내기를 만들어 BFS가 묶음으로 돈다([130](../research/130-batch-executor-frontiers.md): 93 → 41 ms, 계층 7.7). 간선당 명령은 211개(계층 38.5)이고, 후보만 골라 돌게 해도 연산마다 레인을 훑는 꼴의 바닥이 약 50개라, 계층을 넘으려면 합친 연산의 표가 필요하다. +작성일: 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). 4단계의 앞 절반(덧붙이기), 5단계(점화식), 칸에 더하기, 걷기를 만들었다([128](../research/128-batch-executor-tables-and-costs.md): join이 실행기의 423에서 161 ms, 계층 74). 남은 값은 덧붙이기 하나 약 100 ns, join의 점화식 회차당 약 60 ns, 레인이 적은 중첩 몸체의 해석이고, BFS는 아직 묶이지 않는다(바뀌는 개수, 커지기만 하는 걸러내기, 중첩 걷기가 필요하다). 그 셋을 줄였다([129](../research/129-batch-executor-costs-cut.md): 한꺼번에 덧붙이기, 점화식을 레지스터 꼴로, 차례 없는 모으기; knapsack 10.8, matmul 36.4, join 118 ms, roots 8.87로 계층 9.57보다 빠름). BFS를 셈하니 차례대로 할 꼬리가 간선의 약 10%라, 꼬리를 묶음의 C로 하고 간선을 레인으로 펼치면 계층과 겨룰 만하다(약 6–7 ms 예상). 가드, 묶음 안의 걷기(함께, 펼치기), 걸러내기를 만들어 BFS가 묶음으로 돈다([130](../research/130-batch-executor-frontiers.md): 93 → 41 ms, 계층 7.7). 간선당 명령은 211개(계층 38.5)이고, 후보만 골라 돌게 해도 연산마다 레인을 훑는 꼴의 바닥이 약 50개라, 계층을 넘으려면 합친 연산의 표가 필요하다. 사용자는 범용의 길을 택했다(2026-10-08). 같은 코드의 값 다시 쓰기, `if !c`의 분기 하나, 고르기(적은 레인의 갈래를 그 레인만), 비트로 묻기, 아핀 `first + i`로 BFS가 간선당 112개, 24.0 ms가 됐다([131](../research/131-batch-executor-general-cuts.md)); 범용의 바닥은 계층의 약 2배로 보인다. [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/131-batch-executor-general-cuts.md b/docs/research/131-batch-executor-general-cuts.md new file mode 100644 index 0000000..0827a9d --- /dev/null +++ b/docs/research/131-batch-executor-general-cuts.md @@ -0,0 +1,95 @@ +# 131 — 범용 장치로 값 줄이기: 같은 코드의 값 다시 쓰기, 고르기, 비트로 묻기 + +2026-10-08. [130](130-batch-executor-frontiers.md)이 BFS로 보인 간선당 명령 211개(기계어 38.5)를, 어떤 코드에도 듣는 장치로만 +줄였다. 사용자의 방향은 범용이다. 스위트의 꼴만 골라 합친 연산의 표를 만들지 않는다. 코드는 `589e669`와 `735ba81`이다. 같은 방법으로 쟀다 +(`041-batches/runs.sh`, 빌드 `b9` = `735ba81`, 11번의 중앙값; 탐색만의 명령은 `041-batches/search/`, `2026-10-08-search-counts.jsonl`). + +## 쉬운 말로 + +- **BFS가 간선당 211개에서 112개가 됐다.** 41.0 ms에서 24.0 ms다(기계어 7.7, C 9.9). 기계어의 3.1배다(5.4배였다). +- **바꾼 것은 다섯 가지 범용 장치다.** 같은 코드로 다시 계산하는 값을 한 번만 계산하기, `if !c`를 분기 하나로, 레인이 적은 갈래를 + 그 레인만 모아 돌기(고르기), 키가 비트로 있는 리스트에 비트로 묻기, `L.first + i`를 아핀 값으로. 어느 것도 BFS만을 위한 것이 + 아니다. 다만 knapsack과 matmul은 약 10% 느려졌다(아래: 패스의 코드가 커진 값으로 보이며, 원인을 다 가르지는 못했다). +- **이 꼴의 바닥은 기계어의 약 2배로 보인다.** 간선마다 레인을 한 번씩 훑는 일(모으기, 묻기, 분기, 고르기, 덩어리 만들기)이 약 40개, + 노드마다 약 20개, 후보마다 약 250개(간선당 26개)다. 레인 훑기는 연산마다 메모리를 오가고, 기계어는 레지스터에서 한다. 더 줄이려면 + 훑기를 합쳐야 하는데, 그것은 범용이 아니라 꼴의 표다. + +## 묶음 + +| 일 | 기계어 (JIT) | 묶음 (`PURE_JIT=0`) | 실행기만 | C | 묶음의 명령 / 실행기만 | +|---|---:|---:|---:|---:|---:| +| roots | 9.57 ms | **8.97** | 130.5 | 6.86 | 1.69억 / 27.56억 | +| knapsack | 7.93 | 11.45 (b7 10.32) | 1,040.7 | 6.25 | 3.94억 / 216.88억 | +| matmul | 14.24 | 39.98 (36.51) | 914.9 | 7.58 | 9.68억 / 214.37억 | +| join | 84.6 | 116.9 (117.8) | 435.1 | 31.7 | 18.93억 / 76.70억 | +| sort | 35.2 | 56.4 (56.8) | 77.0 | 26.9 | 10.08억 / 15.03억 | +| BFS | 7.71 | **24.0** (41.0) | 92.7 | 9.90 | 33.74억 / 47.12억 | +| words | 215.5 | 217.6 (214.3) | 209.9 | 39.5 | 39.77억 / 39.34억 | + +- 실행기만의 명령은 모두 그대로다. 기계어의 join이 78.8에서 84.6 ms로 오간 것은 같은 코드라 놓인 자리다. +- **knapsack과 matmul이 느려졌다.** 명령이 3–4%, 사이클이 7–8% 많다. 같은 코드의 값 다시 쓰기의 검사를 끄면 2%가 돌아오고(그래서 + 그런 값이 없는 모듈은 검사를 건너뛰게 했다), 고르기를 꺼도 나머지는 그대로다. 패스 함수가 커져 인라인과 배치가 바뀐 값으로 보인다. + 남겨 둔다. + +## 만든 것 + +- **같은 코드의 값 다시 쓰기**(`plan_reuse`, 로드 때). 묶음 블록 안에서 값 하나를 만드는 코드 조각(스택 높이로 자른 부분 나무)이 + 앞선 조각과 같고(`same_code`), 둘이 같은 가장 안쪽 블록의 한 줄에 있고(사이에 `JMP`·루프·`break`·착지 없음; `JZ`는 레인을 + 좁히기만 한다), 사이에서 조각이 읽는 지역 변수를 쓰거나 리스트를 바꾸지 않으면, 뒤의 조각은 앞의 값을 그대로 쓴다. 앞의 값은 + 묶음이 쥔다(버퍼를 놓지 않는다). 모듈은 조각의 시작마다 `reuse_from`, `reuse_length`를 둔다. BFS의 `e.to`를 세 번 모으던 것이 + 한 번이 됐다. +- **`if !c`는 분기 하나.** `NOT` 바로 뒤가 `JZ`면 `NOT`을 돌지 않고 뒤집어 가른다. 레인 세기도 같은 루프에서 한다. +- **고르기**(`select`, `deselect`). 가른 뒤 가는 레인이 4분의 1 이하이고 갈래가 곧은 코드(안에 분기·루프·`break` 없음, 끝의 `JMP`는 + else)면, 그 레인들만 모아(`sel[j]`) 갈래를 돈다. 갈래 안에서 바깥 값을 읽으면 `sel`로 모은다(펼친 걷기 안에서는 항목의 바깥 + 레인을 거쳐 한 번에). 모으기는 바깥 레인의 몫에 더한다. 갈래가 쓴 지역 변수는 갈래가 끝날 때 바깥 레인으로 돌려놓는다(바깥의 + 값은 그대로, 가른 레인만 새 값; 일부 레인에만 썼으면 그 마스크로). 미룬 쓰기와 칸에 더하기는 기록할 때 바깥 레인으로 펼친다. + 덧붙이기는 모은 레인의 칸을 `sel`과 함께 두어 끝의 처리가 그 레인만 돈다. 걸러내기의 몸체가 고르기면, 모은 회차의 기록도 후보만 + 돈다. `PURE_SELECT=all`은 레인 수와 상관없이 모든 곧은 갈래를 고르게 해서(시험용), 무작위 시험이 이 길을 다 지나게 한다. +- **비트로 묻기.** 키를 비트로 두는 리스트(`pure_list_bits`)의 `has`는 레인 루프 하나다. 걸러내기의 결정도 그 비트와 우리 비트 + (모은 회차가 더하는 키를 표시, 끝에 지움)로 한다. 해시 집합이 없어졌다. 비트가 없는 키(정수가 아니거나 범위 밖)는 없는 키다 + (`pure_list_contains`와 같다). 처음에 이것을 "있는 키"로 잘못 두어 무작위 시험 하나가 작업량에서 어긋났고, 고쳤다. +- **`L.first + i`는 아핀.** 균일 값과 아핀 값을 더한 자리가 리스트 안이면 아핀 값이다(전에는 레인마다 계산해 수 레인이 됐다). 그 + 뒤의 읽기가 보폭 읽기가 되고 범위 검사가 양 끝만 본다. +- **조각의 양 끝을 한 번에**(`pure_list_slice`). 걷기의 조각 경계 둘을 한 호출로 찾는다. 묶인 리스트의 정수 키면 버킷으로 바로 간다. +- **모은 회차의 기록.** 키와 칸을 수 레인으로 한 번 만들고 그대로 복사한다(레인마다 값의 꼴을 묻지 않는다). + +## 무엇에 값을 내는가 (BFS, 간선당 명령) + +| 단계 | 간선당 명령 | ms | +|---|---:|---:| +| 130 (`b7`) | 211 | 41.0 | +| 같은 코드의 값 다시 쓰기, `if !c` | 205 | 39.0 | +| 고르기 | 146 | 30.2 | +| 비트로 묻기, 아핀 `first + i`, 바깥 값 한 번에 | 121 | 24.8 | +| 모은 회차의 기록, 조각 양 끝 | **112** | **24.0** | +| 기계어 | 38.5 | 7.7 | + +프런티어 합(간선의 `to`만 더하기)은 70 → 56개다(기계어 35). 사이클로는 BFS 81 → 57(기계어 19), 프런티어 26 → 20(15)이다. + +남은 112개를 표본으로 나누면 대략 이렇다. +- 간선마다 레인을 훑는 일 약 45개: `to` 모으기 12, 비트로 묻기 11, 뒤집어 가르기와 고르기 7, 덩어리 만들기 7, 나머지 디스패치. +- 노드마다 약 20개(간선당): 조각 경계, 레인마다의 수와 작업량. +- 후보(간선의 10.5%)마다 약 250개(간선당 26개): 기록 50, 결정 90, 넣기 110(`pure_list_add_many`의 폭 검사와 칸 쓰기, 몫 셈). +- 묶음마다의 고정 값은 작다. + +**바닥.** 간선마다의 훑기는 연산마다 레인 배열을 메모리로 오가며 요소당 4–12개가 든다. 기계어는 같은 일을 레지스터에서 요소당 +1–2개로 한다. 훑기를 줄이지 않고는 간선당 약 45개 아래로 가지 못하고, 노드와 후보의 몫을 반으로 줄여도 70개쯤이다. 기계어의 +1.8배다. 훑기를 합치는 것은 꼴의 표라서 범용의 길에서는 여기가 바닥이다. 후보의 넣기(110개)는 리스트 쪽 C라 더 줄일 수 있다. + +## 뜻과 맞음 + +- 시드 1, 2, 3, 5, 23의 무작위 프로그램이 다섯 길에서 같았다. `PURE_SELECT=all`로 모든 곧은 갈래를 고르게 해도(시드 1, 2, 3, 5, 23) + 같았다. 처음 돌렸을 때 시드 23의 프로그램 하나가 작업량에서 어긋났다(위의 비트 없는 키). 고치고 다시 돌려 같았다. +- 고정 소스 여섯(`BATCH`, `NESTED`, `RECUR`, `APPEND`, `FRONT`, `GRAPH`)이 세 길에서 같았다(기본과 `PURE_SELECT=all`). +- 단위 시험 세 모드, ASan(실행기 모드), aup의 검사 두 모드, Pure의 `make check`가 통과했다. sanitized `pure-run`으로 시드 23의 + 프로그램과 고정 소스를 기본과 `PURE_SELECT=all`로 돌려도 오류가 없었고 출력은 실행기만으로 돈 것과 같았다. +- 실행기만의 명령은 그대로다(`run`은 건드리지 않았다). + +## 다음 + +- 후보의 넣기를 리스트 쪽에서 줄이기(폭 검사를 한 번에, 비트와 칸을 함께). +- 고르기를 중첩 루프의 회차에도(roots의 빠진 레인). +- `total = total + A + B` 꼴의 모으기 인식(129). +- knapsack과 matmul의 10%: 패스를 나누거나 묶음 구조체의 뜨거운 자리를 모아 되찾기. +- 기계어를 걷어낼지는 사용자의 몫이다. 범용의 길로는 BFS가 기계어의 약 2배에서 멈춘다. 다른 일은 roots가 기계어보다 빠르고, + knapsack 1.4배, matmul 2.8배, join 1.4배, sort 1.6배다. |
