diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/research/125-tier-first-steps.md | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/docs/research/125-tier-first-steps.md b/docs/research/125-tier-first-steps.md new file mode 100644 index 0000000..ada82c9 --- /dev/null +++ b/docs/research/125-tier-first-steps.md @@ -0,0 +1,80 @@ +# 125 — 최적화 계층의 첫 걸음들: C의 3–300배에서 1.9배로 + +2026-10-05. [023](../design/023-optimizing-tier.md)의 계층(중첩 루프를 SSA로 올려 레지스터의 기계어로 도는 것)과 리스트의 표현을 +하루 동안 고치며 잰 것. 코드와 원시 결과는 [040-against-c](../../research/experiments/040-against-c/)에 있고, 묶음은 매번 +`suite-run` 11번의 중앙값(같은 프로세스의 C와 견줌, `PURE_AHEAD=0`)이다. + +## 쉬운 말로 + +- **일곱 가지 일의 기하 평균이 C의 1.92배다.** [124](124-against-c.md)에서는 3–300배였다(배낭과 행렬 곱이 300배, 조인 22배, 텍스트 + 12배). 일마다 1.25배 안이라는 023의 기준은 배낭(1.19배) 하나가 맞고, 기하 평균 1.1배는 아직 멀다. +- **가장 크게 줄인 것은 계층보다 표현이었다.** 차례를 어긴 수 키의 리스트를 해시 표의 칸에 두고(HASHED) 위치가 필요할 때만 정렬하자 + 조인은 531 ms에서 74, 정렬은 98에서 37이 됐다. 텍스트 키도 같은 표로 찾게 하자 텍스트 일이 453에서 209가 됐다. +- **계층에서 값이 컸던 것은 측정이 가리킨 것이었다.** 행렬 곱의 안쪽 루프를 어셈블리로 여러 꼴로 써서 재 보니(`forms.c`) 위치를 곱셈으로 + 구하는 것이 2배, 적재 앞의 분기가 1.34배였고 2^53 오버플로 검사는 거의 공짜였다. 칸의 포인터를 루프의 φ로 두자 23.0 ms가 14.2가 됐다. + 짐작으로 명령을 줄인 시도(되돌림 패드, 줄 밖 비트맵, 상수 나눗셈의 곱셈화)는 0–1%거나 오히려 느려서 되돌렸다. +- **지금의 앱은 그대로다.** TPC-C와 은행 질의를 `5fffb63`과 여섯 쌍 번갈아 재니 TPC-C의 중앙값이 −1.2%(쌍마다 −5.2…+1.4%), 질의의 + 중앙값은 1% 안이고, 질의의 명령 수는 −0.1…+0.5%다. +- **새 검사가 버그 넷을 잡았다.** 무작위 프로그램에 작업량 예산과 작업량 비교를 더하자 022부터 있던 것(바깥 루프의 작업량이 모자랄 때 + LOOP 명령에서 빈 스택으로 포기하던 것)과 계층의 첫 날부터 있던 것(if가 만나는 곳의 φ가 루프 밖으로 끌려 나가던 것)이 나왔다. 둘 다 + 고정 시험(`MERGES_SOURCE`, `BUDGET_SOURCE`)으로 남겼다. +- **남은 것.** 텍스트 일(5.6배)은 실행기가 돈다. 루프가 텍스트를 만들고(`"w{n}"`을 한 바퀴에 세 번, aup의 `let`은 텍스트를 담지 못한다) + 텍스트 연산은 기계어가 없다. BFS(1.6배)는 계층이 키 구간의 걷기와 덧붙이기를 아직 못 한다. 조인(2.5배)은 메모리 미스가 쥐고 있다. + +## 묶음 + +| 일 | `5fffb63` (124) | `11f6d9c` 계층 처음 | `e01dae9` | C | +|---|---:|---:|---:|---:| +| roots (실수 루프) | 3.3배 | 14.0 ms | 9.59 ms (1.40배) | 6.87 | +| knapsack (정수 DP) | 300배 | 38.8 | 7.54 (1.19배) | 6.32 | +| sort (정렬) | 4.8배 | — | 36.7 (1.37배) | 26.8 | +| join (해시 조인) | 22배 | — | 78.4 (2.54배) | 30.8 | +| words (텍스트 셈) | 12배 | — | 209 (5.58배) | 37.5 | +| matmul (행렬 곱) | 304배 | 48.6 | 14.2 (1.85배) | 7.70 | +| BFS (미리 읽기 없이) | 1.57배 | — | 16.0 (1.61배) | 9.90 | +| 기하 평균 | — | — | **1.92배** | | + +미리 읽기를 둘 다 하면 BFS는 1.04배다. 실행기만으로는(`PURE_JIT=0`) roots 131 ms, 배낭 1,058, 행렬 곱 916이다. + +## 바꾼 것, 차례로 + +| 커밋 | 바꾼 것 | 일 | +|---|---|---| +| `cfefd45` | 루프를 열 때 아핀 키의 양 끝을 한 번 검사(BCE), 상수를 명령에, 한 바이트 열을 네 바이트로 | 배낭 38.8→16.5, 행렬 48.6→25.7 | +| `abe0412` | 안쪽 셈 루프의 작업량을 열 때 한꺼번에(나가면 돌려줌), 비교와 분기를 합침, 꽉 찬 리스트는 비트 없이 | 배낭 16.5→8.5 | +| `a3ed5b6` | 래치에서 나갈 때 회차를 next−1로(뒤로 가는 이동 없음), 2의 거듭제곱으로 나누기를 역수 곱으로, 2^53 검사 3명령 | roots 14.1→12.2, 배낭 8.5→7.8 | +| `8d68451` | HASHED: 차례를 어긴 수 키를 해시 표의 칸에, 위치가 필요할 때 기수 정렬; 계층이 키로만 찾을 때 줄 밖 탐사 | 조인 531→108, 정렬 98→58 | +| `082a3dc` | 텍스트 키도 HASHED(FNV-1a, 칸의 키와 바이트 비교), 찾은 자리에 쓰기(PLAN_ROW) | 텍스트 453→217 | +| `b56341e` | 표를 4/5까지(100,000 키가 1 MB: L3 안), 일반 코드의 APPEND가 칸에 바로, 양수로 나눈 정수는 −0이 아님 | 조인 108→84, 정렬 58→37 | +| `e4b4694` | 한 줄로 이어 읽히는 수 산수는 마지막만 유한 검사, 스냅숏의 상수는 상수로, 장부 루프에서도 HASHED에 바로 | roots 12.2→10.9, 조인 84→74 | +| `6466b9e` | 포인터 귀납 변수: 아핀 키의 칸 주소를 루프 머리의 φ로 | 행렬 23.0→14.2 | +| `e01dae9` | 유한 검사를 정수로(지수 비트) | roots 10.9→9.6 | + +## 어셈블리로 잰 꼴 + +`forms.c`(행렬 곱의 안쪽 루프, 11번의 중앙값): 계층의 꼴 20.7 ms, 검사와 비트 없이 20.1, 포인터와 곱하기-더하기 13.1, 포인터와 +곱·합 따로 10.0, 거기에 두 검사 10.2, 적재 앞에 비트 13.4, 정적 배열의 C 3.8(바깥 루프를 벡터로 묶는다). + +`newton.c`(roots의 루프): 계층의 꼴(next를 따로 두고 뒤로 가며 옮김) 7.7 ms, gcc의 꼴(next를 r의 레지스터에) 7.0, 수로 하는 유한 검사를 +더하면 9.7, 정수로 하면 8.7. 같은 생각으로 0으로 나누기 검사를 정수로 하면 오히려 느렸다. + +측정이 짐작을 두 번 뒤집었다. 명령 18개를 16개로 줄인 것(`mov` 하나와 줄 밖 비트맵)은 0%였고, 상수 나눗셈을 곱셈으로 바꾼 것은 +조인을 2–4% 느리게 했다(메모리에 묶인 루프에서 수의 나눗셈은 정수 파이프 밖에서 공짜에 가깝다). + +## 뜻과 맞음 + +- 무작위 계층 시험(`module_tier`)이 이제 작업량을 비교하고 작은 예산 넷을 돈다. 리스트는 DENSE(꽉 참/빈틈), HASHED(키로만), 키를 회차와 + 함께 움직이는 훑기를 섞는다. 지금의 생성기로 시드 6개(프로그램 약 800개)가 같은 답을 낸다. +- 고정 시험: `MERGES_SOURCE`(if가 만나는 곳의 φ), `BUDGET_SOURCE`(작업량이 모자란 바깥 루프), `CHAIN_SOURCE`(넘치는 수의 줄). +- 리스트: `arrays_equivalence`가 HASHED를, `text_keys_equivalence`가 텍스트 키의 HASHED와 위치를 묻는 덧붙이기를 트리와 견준다. +- 저장된 리스트(트리)의 길에서 HASHED 확인을 빼, 은행 질의의 명령 증가를 0.3–0.8%에서 0.5% 안으로 줄였다. + +## 다음 + +- **텍스트**: 텍스트를 지역 값으로 담기(aup의 `let`과 Pure의 지역 칸)와 텍스트 연산의 기계어. 이 일의 남은 시간 대부분이 같은 텍스트를 + 세 번 만드는 데와 실행기의 디스패치에 있다(디스패치 약 20%, 해시와 탐사 약 11%, 수의 서식과 텍스트 만들기 약 13%). +- **BFS**: 계층이 키 구간의 걷기(sliced EACH)와 이벤트 리스트의 덧붙이기를 하게. +- **루프 묶음 일반**: 레지스터가 모자라면 계층이 묶음을 포기한다(행렬 곱이 그랬다). 루프 밖에서만 쓰이는 진입 값을 다시 읽거나 프레임에 + 두는 스필이 필요하다. 꽉 차지 않은 DENSE를 키로 훑는 루프는 지금 일반 코드로 돈다(루프를 열 때 읽을 키만 확인하는 것이 다음). +- **뜻의 값**: 검사 없는 계층(`PURE_UNCHECKED`)으로 일마다 검사의 몫을 재는 손잡이는 아직 없다. roots에서 임시로 잰 유한 검사의 + 몫은 지연 전 27%였다. |
