From 402c19311b247889b36ff3be834d9fab19555af7 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 17:59:47 +0100 Subject: Count instructions where timing cannot tell 3% apart (110) 016's second stage passed its criterion: six interleaved pairs against ee21aec gave TPC-C (RAM, W=1) -0.6% at the median and the bank's queries 0.0% to +2.2%, no run interfered. The ranks query was slower all six times, and chasing it showed that on this Pi timing cannot separate 1-3%: two servers asked in turns disagree with themselves (ee21aec to e02bfc9 +1.6%, e02bfc9 to 481642f -1.2%, ee21aec to 481642f +2.8%), and a query whose instructions grew 0.6% ran 3.5% faster. Instructions do not move so: pcount.c counts the server's user-mode instructions with perf_event_open (no perf here), and psample.c samples where they go. ranks had grown 0.4% in instructions (the rest was where code and data landed), per_branch 3.6% and tables 20 instructions a row, which 07047c3 halved. The six pairs of 07047c3 were interfered (another session's build, load 4-12) and are kept apart, unused. --- docs/research/110-counting-instead-of-timing.md | 104 ++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 docs/research/110-counting-instead-of-timing.md (limited to 'docs/research') diff --git a/docs/research/110-counting-instead-of-timing.md b/docs/research/110-counting-instead-of-timing.md new file mode 100644 index 0000000..c84a06c --- /dev/null +++ b/docs/research/110-counting-instead-of-timing.md @@ -0,0 +1,104 @@ +# 110 — i64가 수의 경로에 더한 것: 시간 대신 명령을 센다 + +2026-09-30. [016](../design/016-bool-and-i64.md)의 2단계(`i64`)가 수의 경로를 느리게 하지 않는지 잰다. 기준은 구현하기 전에 +016에 적어 커밋했다(`9a03e72`). 모두 초록이고, 은행 질의([091](091-queries.md))와 TPC-C(RAM, W=1)를 전과 후 번갈아 여섯 쌍 +재서 중앙값이 3% 넘게 느려지지 않는다. + +전은 `9a03e72`(i64 전, bool까지)의 pure-serve다. 두 쪽이 같은 바이트의 모듈을 돌린다. i64 전과 후의 aup가 `tpcc.aup`, +`bank.aup`, 은행의 질의 넷, 예제 18개를 같은 바이트로 빌드하므로 다른 것은 런타임뿐이다. + +## 쉬운 말로 + +- **기준은 통과했다.** TPC-C 처리량의 중앙값 −0.6%, 은행 질의 0.0%에서 +2.2%. 방해받은 실행은 없었다. +- **그런데 순위 질의가 여섯 번 모두 느렸다.** 까닭을 찾다가, 이 Pi에서 시간 비교는 3% 안쪽을 가리지 못한다는 것을 확인했다. + 같은 두 빌드를 다시 재면 차이가 바뀌고, 셋을 둘씩 재면 앞뒤가 맞지 않는다. 아무것도 바꾸지 않은 질의가 3.5% "빨라지기도" + 했다. 프로세스마다, 빌드마다 코드와 데이터가 놓이는 자리가 달라서다. +- **실행한 명령의 수는 흔들리지 않는다.** 한 스레드로 실은 이체의 표를 읽는 질의는 프로세스가 달라도 명령이 0.03% 안에서 + 같다. 이 Pi에는 perf가 없어서 + `perf_event_open`만으로 서버의 명령을 세는 도구와, 명령 2만 개마다 어디를 실행하는지 뜨는 샘플러를 만들었다. +- **명령으로 보니 둘은 달랐다.** 순위 질의는 명령이 0.4% 늘었을 뿐이다(느려진 것은 자리 탓이다). 반대로 지점별 합계는 명령이 + 3.6% 늘었는데 시간에는 드러나지 않았다. 표는 줄마다 명령 20개가 늘었다. +- **두 곳을 고쳤다**(`fcc1f67`). i64 폴드는 잎 단위로 돌지 않는데도 잎 단위의 폴드가 항목마다 i64인지 물었다. 그 길은 수만 + 받게 했다. 그리고 칸의 종류가 하나 늘자 칸을 읽는 함수가 인라인되지 않아 항목을 읽을 때마다 호출이 하나 생겼다. 수·bool· + i64의 칸은 8바이트를 그대로 읽게 해 분기가 전처럼 하나가 되고 다시 인라인된다. 늘어난 명령이 절반쯤 줄었다(표 +1.3%, 지점별 + 합계 +1.2%). + +## 결과 + +원시 결과와 명령은 [025-i64-cost](../../research/experiments/025-i64-cost/)에 있다(`runs.sh`, `ab.py`, `ab-sessions.sh`, +`count.py`, 그리고 도구 `pcount.c`와 `psample.c`). + +**여섯 쌍**(TPC-C는 64터미널 20초, 은행 질의는 10만 계좌에 질의마다 15번 물은 중앙값. 후는 `fc65d66`). 부하 평균 0.4–1.8, +소유자의 코어 몫은 모든 실행에서 0.90–0.92였다. + +| 쌍 | 초당 거래(전 → 후) | 거래당 CPU(µs) | 소유자의 코어 몫 | +|---|---:|---:|---:| +| 1 | 42,603 → 42,974 (+0.9%) | 44.2 → 44.9 | 0.90 → 0.90 | +| 2 | 44,695 → 43,683 (−2.3%) | 43.3 → 43.7 | 0.92 → 0.91 | +| 3 | 43,401 → 44,502 (+2.5%) | 44.4 → 44.0 | 0.90 → 0.92 | +| 4 | 44,200 → 43,816 (−0.9%) | 43.0 → 44.5 | 0.90 → 0.91 | +| 5 | 44,277 → 43,592 (−1.5%) | 44.1 → 44.6 | 0.91 → 0.91 | +| 6 | 43,843 → 44,342 (+1.1%) | 43.7 → 43.9 | 0.91 → 0.91 | + +중앙값은 44,022 → 43,750(−0.6%)이고, 거래당 CPU는 43.9 → 44.3 µs(+0.8%)다. + +| 은행 질의 | 중앙값(ms, 전 → 후) | +|---|---:| +| 지점별 합계(`per_branch`) | 5.27 → 5.30 (+0.6%) | +| 흑자 계좌 수(`in_credit`) | 0.84 → 0.84 (0.0%) | +| 큰 이체의 조인(`large`) | 0.38 → 0.385 (+1.3%) | +| 순위(`ranks`) | 6.69 → 6.84 (+2.2%, 여섯 번 모두 후가 느림) | + +**시간의 흔들림.** 두 서버를 함께 띄우고 같은 계좌를 실은 뒤 질의를 번갈아 물었다(`ab.py`, 12라운드, 라운드마다 30번). +한 쪽에 프로세스 하나씩이다. + +| 질의 | 9a03e72 → fc65d66 | 9a03e72 → 600f576 | 600f576 → fc65d66 | 9a03e72 → fc65d66 (다시) | +|---|---:|---:|---:|---:| +| `per_branch` | +1.6% | +0.1% | 0.0% | +1.8% | +| `in_credit` | −0.1% | +4.7% | −6.5% | +0.7% | +| `large` | +1.4% | −3.1% | 0.0% | +1.7% | +| `ranks` | +2.8% | +1.6% | −1.2% | +2.8% | + +가운데 둘을 더하면 오른쪽 끝이 나와야 하지만 맞지 않는다(`ranks`는 +0.4%여야 할 것이 +2.8%). `600f576`은 값 칸을 겹치기만 한 +커밋이다. 한 프로세스가 가진 자리의 운이 그 비교 전체에 실린다. 그래서 세션마다 새 프로세스를 띄워 여덟 번 쟀다 +(`ab-sessions.sh`). `per_branch` −0.2%, `in_credit` −3.5%, `large` +1.0%, `ranks` +1.2%(여덟 번 모두 후가 느림, +0.8%에서 ++2.6%). 흑자 계좌 수는 명령이 0.6% 늘었는데(아래) 시간은 3.5% 줄었다. 이 크기의 차이는 시간으로는 가릴 수 없다. + +**명령 수**(`count.py`, 서버의 모든 스레드의 사용자 모드 명령, 한 번 물을 때. 새 프로세스 둘의 평균, 부하와 상관없다). +`none`, `field`, `found`는 이체 2만 건 위의 표다. 남는 줄이 없는 것, 1만 줄에 필드 하나, 1만 줄에 계좌를 키로 찾아 읽는 +것이다. + +| 질의 | 9a03e72 | fc65d66 | fcc1f67 | +|---|---:|---:|---:| +| `per_branch` | 26.67M | 27.64M (+3.6%) | 27.00M (+1.2%) | +| `in_credit` | 3.45M | 3.47M (+0.6%) | 3.47M (+0.8%) | +| `large` | 1.420M | 1.451M (+2.2%) | 1.438M (+1.3%) | +| `ranks` | 24.08M | 24.17M (+0.4%) | 24.17M (+0.4%) | +| `none` | 0.600M | 0.608M (+1.2%) | 0.606M (+0.9%) | +| `field` | 8.579M | 8.778M (+2.3%) | 8.695M (+1.4%) | +| `found` | 15.443M | 15.845M (+2.6%) | 15.640M (+1.3%) | + +이체를 읽는 질의(`large`, `none`, `field`, `found`)는 두 프로세스의 차이가 0.03% 안이다(`none`은 명령 하나까지 같다). +계좌를 읽는 질의는 1% 안팎으로 다르다. 계좌는 여덟 스레드로 실어 들어온 순서가 매번 다르고, 그래서 트리의 잎이 다르다(이체는 +한 스레드로 싣는다). 조용할 때 잰 사이클로 `ranks`는 15.07M → 15.49M(+2.8%)이었다. 명령은 0.4%만 늘었으니 나머지는 자리다. + +**표본**(`count.py --profile field`, 300번 물은 동안 명령 20,011개마다 하나, 빌드마다 약 8만 6천 개). 고치기 전(`fc65d66`)에는 +`cell_at`이 따로 불리는 함수로 나타난다(0% → 2.98%). 전에는 `pure_list_get` 안에 인라인되어 있었다. 기계어로 보면 칸의 종류를 +묻는 검사가 `cbz` 하나에서 네 명령이 되며 커졌다. 고친 뒤(`fcc1f67`)에는 다시 0%다. + +**고친 뒤의 여섯 쌍**(`fcc1f67-interfered`). 둘째 라운드부터 다른 세션의 빌드가 부하 평균을 4–12로 올렸다. 소유자의 코어 몫이 +0.55–0.89로 떨어졌고, 쌍마다 −29%에서 +80%까지 흔들린다. 판정에 쓰지 않는다. 지우지 않고 남긴다. + +## 판정 + +- **016의 2단계는 들어간다.** 기준을 채웠다(`fc65d66`의 여섯 쌍). 고친 뒤(`fcc1f67`)는 수의 경로에서 그보다 명령이 적다. +- **남은 것**은 명령 +0.4%에서 +1.4%다. 여러 함수에 한두 명령씩 흩어져 있다. 칸의 종류 검사(`text` 표시 한 바이트에서 종류 + 번호의 비교로), 커진 `run`의 새 경우들이다. 사이클로는 전과 가려지지 않는다. + +## 다음 + +- **수의 경로를 볼 때는 명령도 센다.** 시간의 3% 기준은 그대로 두되, 이 Pi에서 1–3%는 자리의 흔들림이다. 명령 수(`count.py`)는 + 결정적이라 1% 차이도 보인다. 다음 기준부터 "명령이 1% 넘게 늘지 않는다"를 함께 적는다. +- **표본을 뜨는 도구가 생겼다.** 109가 멈춘 곳(캐시보다 큰 목록을 찾는 걸음)을 다시 볼 때 소유자의 표본을 스무 개가 아니라 + 수만 개 뜰 수 있다. 명령이 아니라 사이클로 뜨면 기다리는 곳이 보인다(`psample.c`의 이벤트만 바꾸면 된다). +- 남은 1%를 걷으려면 필드마다 `text` 표시 한 바이트를 레이아웃에 다시 두면 된다. 지금은 하지 않는다. -- cgit v1.2.3