diff options
| author | eunhhu <[email protected]> | 2026-10-19 06:18:49 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-19 06:18:49 +0100 |
| commit | 6215b1177e377b512aaa1296faa6311f809135f2 (patch) | |
| tree | d2be13b3e5d4cb0b7150b0a6d186fc313842de18 /docs | |
| parent | 8071832bc7857634f66c94592b27b188c882ea54 (diff) | |
| download | pure-6215b1177e377b512aaa1296faa6311f809135f2.tar.gz pure-6215b1177e377b512aaa1296faa6311f809135f2.zip | |
140's 8, box layout, written in aup with no new opcode (aup's examples/layout.aup: a subset of CSS Flexbox 1 written down first, one line, row or column, padding, gap, six justify-content, four align-items, grow and shrink with 9.7's loop, cross sizes stretched, nothing sized by content): 2,000 random trees (29,388 nodes; 7,659 items frozen at their padding by the loop) are Taffy 0.15's within 0.01 (layout_taffy, built by cargo; the worst 8.3e-5) and layout.py's bit for bit on the three paths, the paths the same; the single-pass subset first tried refused 66% of trees, so the loop came in; Taffy given min sizes equal to the padding (given 0 it floors at 0 while resolving lengths, where 9.7 floors the content box), the reference adding from the left (sum compensates since Python 3.12); module_specs in make check: 100 trees (tests/specs/layout); bars: 116 lines against Python's 54, 4,754 ns a node, Taffy's 644 (7.4x), pure Python 5,396 (Pure 1.14x faster), 1,494 instructions a node (9.7's loop 44%); four frictions; aup's rows read by position no longer copy (99,067 ns a node before); the suite table's row 8; CHECKPOINT's direction row
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/research/140-spec-suite.md | 79 |
1 files changed, 78 insertions, 1 deletions
diff --git a/docs/research/140-spec-suite.md b/docs/research/140-spec-suite.md index 1da8f4b..89d92e8 100644 --- a/docs/research/140-spec-suite.md +++ b/docs/research/140-spec-suite.md @@ -54,7 +54,7 @@ | 5 | UTF-8과 줄 나누기(UAX #14) | 텍스트 | 바이트, 코드 포인트, 속성 표 | Unicode의 `LineBreakTest.txt` | 못 쓴다(글자) | 썼다: LineBreakTest.txt 16,672줄 모두와 무작위 2,000줄이 맞다. 줄 1.8배, C의 199배(아래) | | 6 | 관계 질의, 그래프 탐색 | 데이터 모델 | 리스트, 키, 접기, 이벤트의 리스트 | TPC-C, 은행 질의, 파이썬 모델 | 된다(기준선: [092](092-tpcc.md), 040의 은행 질의, aup `routes.aup`) | 된다. TPC-C가 SQLite와 오차 없이 같다(5단계) | | 7 | 텍스트 편집 | UI 개념 | text, 커서 | 편집기의 참조 모델 대조(aup `make check`) | 된다 | 된다 | -| 8 | 상자 배치(flex 같은) | UI 개념 | 중첩, 재귀 | 기존 배치 구현(Yoga, Taffy)의 결과와 대조 | 못 쓴다(중첩, 재귀) | 중첩과 재귀는 핸들러에서 된다. 보기에서 재귀 fn을 부르지 못한다(탐침). 쓰지 않음 | +| 8 | 상자 배치(flex 같은) | UI 개념 | 중첩, 재귀 | 기존 배치 구현(Yoga, Taffy)의 결과와 대조 | 못 쓴다(중첩, 재귀) | 썼다(Flexbox의 부분집합): 무작위 트리 2,000개가 Taffy와 0.01 안, 파이썬 참조와 비트까지 같다. 줄 2.1배, Taffy의 7.4배(아래) | | 9 | HTTP/1.1 요청을 패킷마다(RFC 9112) | 스트림의 상태 기계 | 바이트, 이벤트를 넘는 상태 | 경계를 아무 데서나 자른 요청 모음 | 못 쓴다(바이트). 지금은 `serve/`의 C다 | 썼다: 스트림 200개를 어디서 잘라도 h11과 같다. 우회 하나(리스트 값의 대입), 줄 1.9배, C의 83배(아래) | | 10 | 재시도, 물러서기, 시간 제한 | 시간 | 시각, 한 번의 타이머 | 시각을 준 시뮬레이션 | 우회(주기 틱 `every`와 `now`) | 썼다(RFC 6298): 사건 241,612개가 같은 규칙의 모델과 같다. 우회 하나(주기 틱: 10 ms면 타임아웃 하나에 틱 2,796개), 줄 1.9배(아래) | | 11 | X25519, ChaCha20-Poly1305 | 상수 시간 암호 | 데이터에 따라 달라지지 않는 시간 | RFC 7748, RFC 8439의 벡터 | aup 밖(설계상, 아래) | 그대로(설계상 aup 밖) | @@ -842,6 +842,83 @@ 번째 후보: HTTP의 유휴 연결 시간 제한, DNS 질의의 재시도. 둘 다 아직 묶음의 명세가 다루지 않는다. - 원본: `rto.py`(모델), `rto_check.py`(맞음, 틱의 값, 줄 수), `2026-10-19-rto.jsonl`. +## 묶음의 8: 상자 배치 (CSS Flexbox 1의 부분집합, 2026-10-19) + +**부분집합(쓰기 전에 정했다).** 막대를 셈하려면 알고리즘의 어디까지를 쓰는지 먼저 정해야 한다. +- 컨테이너: 한 줄(wrap 없음), 방향 row·column(역방향 없음), padding 네 변, 항목 사이의 gap, justify-content 여섯(start, end, + center, space-between, space-around, space-evenly), align-items 넷(start, end, center, stretch). +- 항목: flex-grow와 flex-shrink는 0–3의 정수, flex-basis는 정한 길이, 주축 크기는 auto, 교차 크기는 정한 길이이거나 auto(auto는 + 늘이는 줄에서만). +- 최소 크기는 0이다(그래서 바닥은 항목의 padding이다). 최대 크기, margin, border, 절대 위치, 종횡비, align-self, order는 없다. + box-sizing은 border-box(Taffy의 기본)다. +- 내용으로 정하는 크기(intrinsic sizing)는 없다. 모든 크기가 위에서 아래로 정해진다. 뿌리는 정한 너비와 높이를 가진다. +- 9.7(유연한 길이 결정)은 고정 고리까지 쓴다. 인자가 정수라 "풀린 항목의 인자 합이 1 미만"(4b)은 일어나지 않는다. 그 가지는 이 + 부분집합 밖이다. +- 알려진 구현은 Taffy 0.15(cargo로 받은 Rust, `layout_taffy`)다. 반올림을 끄고, 이진32인 Taffy와 이진64인 파이썬 참조의 차는 + 0.01 안이어야 한다. aup 앱은 파이썬 참조와 비트까지 같아야 한다. +- **고정 고리가 들어온 까닭.** 처음에는 9.7을 한 번에 끝나는 경우만 쓰고, 고리가 필요한 트리(줄이다 padding 밑으로 내려가는 + 항목)를 버렸다. 무작위 트리 6,538개를 뽑아 2,000개를 얻는 동안 4,346개(66%)가 그래서 버려졌다. 그 부분집합은 "늘이기만"을 재는 + 셈이다. 그래서 고리를 썼다. 고리는 돌 때마다 항목을 하나 이상 고정하므로 항목 수만큼 돌면 끝난다. + +**쓴 것.** aup의 `examples/layout.aup`이다. 새 명령도 코어의 C도 없다. +- 트리는 메시지 하나로 온다. 노드의 행(부모의 위치, 방향, 크기, 인자, basis, padding 넷, gap, justify, align)이 앞차례(preorder)로 + 든다. 부모가 아이보다 먼저 오므로, 앱은 행을 차례로 돌며 노드마다 아이들의 상자를 정한다. 크기가 모두 위에서 오므로 재귀가 필요 + 없다. 파이썬 참조(`layout.py`)도 같은 행을 같은 차례로 돈다. +- 상자(x, y, w, h, 부모의 상자에 대해)는 같은 차례의 저장 리스트다. + +**맞음.** +- `layout_check.py`: 무작위 트리 2,000개(노드 29,388개). Taffy의 상자가 모두 파이썬 참조의 0.01 안이다(가장 큰 차 8.3e-5). aup + 앱의 상자는 세 실행 길에서 모두 파이썬 참조와 비트까지 같고, 세 길이 같다. 버린 트리는 없다. +- 9.7의 고리가 padding에서 고정한 항목이 7,659개다(트리 1,399개, 70%). 고리는 시험에서 늘 돈다. +- Taffy와 맞추며 찾은 것 둘. + 1. min-size를 0으로 주면 Taffy는 9.7의 d단계에서 항목을 0에서만 자른다. padding은 항목이 끝에 갖는 크기의 바닥일 뿐이다. 표준은 + 내용 상자를 0에서 자르라고 한다(border-box에서는 padding이 바닥이다). 그래서 Taffy에 min-size로 padding의 합을 준다. 그 값이 + 표준의 규칙이 내는 값이다. Taffy의 버릇을 앱에 베끼지 않았다. + 2. 파이썬은 3.12부터 `sum()`으로 부동소수를 보정해 더한다(Neumaier). 앱은 왼쪽부터 더하므로 참조도 왼쪽부터 더한다(`reduce`). + 처음에는 트리 200개 가운데 15개가 1e-14쯤 어긋났다. +- Pure의 `make check`(`module_specs`)에 들어갔다. 트리 100개(노드 1,518개)를 상자와 함께 `tests/specs/layout`에 두었다. + space-around의 첫 간격을 3으로 나누는 앱은 그 검사에서 떨어진다. + +**막대.** + +| 막대 | 잰 것 | | +|---|---|---| +| 쓸 수 있다 | aup와 Pure만. 새 명령 없음 | 지남 | +| 우회가 없다 | 마찰 넷(아래). 우회라 할 꼴은 없다 | 지남(마찰은 적는다) | +| 줄 수 ≤ 파이썬 참조 | aup 116줄(닫는 괄호만인 줄 17) 대 파이썬 54줄 | **못 미침**(2.1배) | +| C 대비 시간 | 노드당 4,754 ns(묶음), 4,917(라운드), 4,751(모두 컴파일). Taffy(Rust, 트리를 짓고 배치)는 644 ns로, Pure가 **7.4배**다. 순수 파이썬 5,396 ns(Pure가 **1.14배 빠름**) | 기록(막대 없음) | +| 견고하다 | 위의 맞음, 세 길이 같음. 세 기계는 아직 | 일부 | + +**aup에서 고친 것: 메시지의 행을 위치로 읽기.** `tree.nodes[k]`가 행 전체를 리스트 값으로 복사하고(`LIST_VALUE`) 그 복사본에서 +읽었다. 그래서 읽을 때마다 행의 수만큼 들었다. 처음 잰 시간은 노드당 99,067 ns였다. 명령은 노드당 1,539개뿐이라 명령 하나에 +64 ns였다. 행을 그 자리에서 읽게 고치자(aup의 커밋 e5a6209) 4,737 ns가 되었고, 답은 같다. aup, Pure, Vega의 다른 모듈은 하나도 +바뀌지 않는다. aup의 `make check`에 `rows_at`을 넣었다. + +**마찰 넷.** +1. 저장 리스트 항목의 필드 여럿에 한 번에 대입하지 못한다(`boxes[k].x, boxes[k].y = …`). 네 줄로 썼다. +2. 저장 리스트를 비우는 말이 없다. `while boxes.count > 0 { boxes.remove(boxes.last) }`로 썼다. +3. 축을 고르는 길이 없다. 파이썬은 상자의 칸을 번호(`m`, `c`)로 고른다. 레코드의 필드는 번호로 고를 수 없어서 `if d.column { … } + else { … }`가 열두 번 든다. +4. 리스트의 let은 한 줄에 하나다(다섯 줄). 줄 나누기의 마찰 3과 같다. + +**줄을 길게 한 것.** 116 대 54다. 닫는 괄호의 줄을 빼면 99다. +- 레코드 둘(`Node`, `Box`)이 22줄이다. 파이썬은 튜플의 칸을 번호로 읽는다. +- 9.7의 고리가 aup에서 24줄, 파이썬에서 10줄이다. 파이썬은 리스트 내포로 풀린 항목, 인자, 위반을 한 줄씩 짓는다. +- justify의 첫 간격과 사이 간격이 파이썬에서 dict 한 줄씩, aup에서 `if … else if …` 한 줄씩이다(같다). + +**시간이 가는 곳.** 노드당 Pure 명령이 1,494개이고, 명령 하나에 약 3.2 ns가 든다. 앱을 단계마다 끊어 셈했다. +- 아이 찾기(앞차례에서 부모가 그 노드인 행, 노드 수의 제곱의 반)와 상자 만들기가 159개다. +- 항목의 리스트 다섯을 짓는 데 329개다. +- 9.7의 고리가 658개(44%)다. 한 바퀴에 항목마다 세 번 돈다(쓴 크기와 인자, 나눔과 위반, 고정). +- 남은 공간과 위치가 328개다. +- 원본: `layout.py`(파이썬 참조), `layout_check.py`(맞음, 고정한 항목, 줄 수, 경로를 주면 트리를 쓴다), `layout_taffy`(Taffy + 0.15의 오라클과 `--time`, cargo), `layout_time.c`(Pure의 시간, 트리마다의 상자가 참조와 비트까지 같아야 잰다), + `2026-10-19-layout.jsonl`. + +**부분집합이 재지 않는 것.** 내용으로 정하는 크기(min-content 기여, 자동 최소 크기), wrap과 여러 줄, margin과 auto margin, +min과 max가 남는다. Flexbox의 어려운 반은 크기가 아래에서 위로 올라오는 쪽인데, 이 부분집합은 크기가 위에서 오는 반만 잰다. +그 반은 aup로 새 명령 없이 쓰이고, Taffy와 같다. + ## 착수 기준 (다음 일) - **바꿀 결정.** 계산의 핵을 설계한다(027). 위 표의 여덟 후보 가운데 무엇을 어떤 모양으로 받을지 정한다. |
