aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research/007-buffer-retention-results.md
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /docs/research/007-buffer-retention-results.md
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'docs/research/007-buffer-retention-results.md')
-rw-r--r--docs/research/007-buffer-retention-results.md195
1 files changed, 195 insertions, 0 deletions
diff --git a/docs/research/007-buffer-retention-results.md b/docs/research/007-buffer-retention-results.md
new file mode 100644
index 0000000..36fb684
--- /dev/null
+++ b/docs/research/007-buffer-retention-results.md
@@ -0,0 +1,195 @@
+# Pure 연구 보고서 007: 버퍼를 공유할 때와 복사할 때
+
+작성일: 2026-09-22
+상태: **비교 CLI 구현·정확성 검사·Linux AArch64 측정 완료. Pure의 성능 우월성은 확인하지 못함.**
+
+## 이번에 알아낸 것
+
+첫 후보의 문제는 재현됐다. 작은 결과 때문에 큰 원본 버퍼가 오래 유지되면 메모리가 커진다.
+하지만 이번 범위에서는 **일반적인 크기 기준 복사와 필드 선별 읽기만으로 큰 개선이 가능했다.**
+현재 메모리 압력에 따라 복사를 결정하는 후보 정책은 그 단순한 방법보다 뚜렷하게 낫지 않았다.
+이 결과를 근거로 새로운 런타임·ABI·컴파일러를 확대할 이유는 아직 없다.
+
+같은 64개 큐 한도에서 64 KiB 레코드의 64바이트만 남기는 작업을 비교했다.
+공유 방식의 최대 관리 메모리는 약 **4.070 MiB**, 일반 복사 방식은 **0.137 MiB**였다.
+이는 실험 코드가 요청한 저장 공간의 약 30배 차이다. 전체 앱 RAM의 30배 개선이 아니다.
+해당 조건의 Linux RSS 근삿값은 각각 약 5.05 MiB와 1.56 MiB였다.
+
+사용 경험으로 이어질 수 있는 차이는 큰 데이터를 처리할 때 적은 메모리로 작업을 지속하는 것이다.
+다만 메모리가 줄면 큐가 더 길어져 응답이 늦어질 수도 있었다. 버퍼 용량과 지연을 함께 제한해야 한다.
+이번 근거는 합성 입력을 사용한 실제 네이티브 실행 측정이다. 실제 서비스·Mac·Pure 제작 경험의 측정은 아니다.
+
+사용자는 이번에 새 데모를 평가할 필요가 없다. 실행 가능한 산출물은
+[비교 CLI와 재현 방법](../../research/experiments/002-buffer-retention/README.md)이며,
+현재 제작 화면의 문법과 동작은 바꾸지 않았다.
+
+## 구현한 비교
+
+같은 C17 프로그램 안에서 입력 처리·큐·출력·메모리 예산·계측을 공유하고 정책을 바꾼다.
+Pure VM을 호출하지 않으며 새로운 메모리 관리 원리를 구현했다고 주장하지 않는다.
+
+| 방식 | 처리 |
+|---|---|
+| `borrow` | 선택한 부분이 필요할 동안 원본 전체를 보관 |
+| `copy` | 선택한 부분을 별도 버퍼에 복사한 뒤 원본 반납 |
+| `threshold2/4/8` | 원본/선택 버퍼의 할당 용량 비율이 2·4·8 이상이면 복사 |
+| `pressure` | 현재 활성 버퍼가 가용 예산의 절반을 넘으면 더 작은 결과를 복사 |
+| `project` | 입력에서 필요한 부분만 결과 버퍼로 읽고 나머지는 작은 임시 공간으로 읽어서 버림 |
+| `copy_no_pool` | 일반 복사와 같은 정책·용량 구분을 쓰되 버퍼 재사용 캐시를 끔 |
+
+모든 방식에 전체 예산과 큐 개수 제한을 적용했다. 캐시의 빈 버퍼도 예산에 넣고 필요하면 반납한다.
+복사 중 원본과 결과가 동시에 존재하는 공간도 포함했다. 원본 크기만큼 필요한 공간이 없으면
+대기 중 출력을 처리하고, 그것으로도 불가능하면 명시적으로 실패한다.
+
+입력은 길이·선택 범위가 헤더에 담긴 실험용 바이너리 레코드다. 모든 입력 바이트를 실제로 읽는다.
+JSON 파서나 압축 라이브러리를 비교한 것이 아니다. `project`의 범위는 이처럼 필요한 위치를
+알 수 있는 입력이다. 범용 문서에서 같은 효과를 얻으려면 별도 검증이 필요하다.
+
+## 실행 조건과 검증
+
+- 호스트: Raspberry Pi 계열 Linux AArch64, Cortex-A76, GCC 14.2.0. 사용자 Apple Silicon에서 실행하지 않았다.
+- 빌드: C17, `-O2`, 경고를 오류로 처리, 진단용 assert·계측 활성화. 최적화 빌드와 ASan/UBSan 빌드를 따로 검사했다.
+- 입력: 조건별 2,048개 레코드, 약 128~169 MiB. seed 20260922, 크기·선택 범위·바이트 생성 규칙과 해시 보관.
+- 일반 조건: 관리 공간 8 MiB, 큐 최대 512개. 좁은 예산 조건은 2 MiB. 별도 민감도 검사는 큐를 64개로 제한했다.
+- 느린 소비자: 각 결과는 enqueue 뒤 최소 5 ms 보관, 연속 출력 완료 사이 최소 20 μs 간격.
+ 실제 sleep과 파일 I/O를 수행하지만 이 지연은 합성 조건이다. OS 기상 지연 때문에 정확히 20 μs마다 출력되는 것은 아니다.
+- 6개 조건 × 8개 방식 × 5회 = **240개 측정**. 방식별 워밍업 후 각 반복에서 순서를 섞었고 모든 출력 해시가 일치했다.
+- 일반 빌드와 ASan/UBSan 합계 **88개 검사 그룹** 통과. 바이트 단위 출력 비교, 잘린 입력,
+ 잘못된 범위, 예산 부족, 완료 출력의 순서, 취소, SIGTERM, 실제 출력 실패(`/dev/full`), 버퍼 회수를 검사했다.
+
+입력 생성·빌드·결과 검사는 시간 측정 밖이다. 측정에는 읽기·쓰기·복사·대기·계측·버퍼 정리가 포함된다.
+파일 캐시를 사용하는 실행이며 출력에 `fsync`를 하지 않는다. 한 CPU로 고정했으나 온도·전력·다른 작업은 통제하지 않았다.
+
+## 결과 1: 같은 큐 한도에서도 기존 복사 정책이 효과적
+
+64 KiB 레코드에서 64바이트를 선택하고, 모든 방식의 큐를 64개로 제한했다.
+아래는 5회 중앙값이다. 메모리는 고정 관리 공간·임시 공간·버퍼·빈 캐시를 포함한 요청 바이트다.
+시간은 전체 2,048개 처리가 끝날 때까지의 값이며 합성 대기가 포함된다.
+
+| 방식 | 최대 관리 공간 MiB | RSS 근사 MiB | 완료 시간 ms | 프로세스 내부 P99 상한 ms |
+|---|---:|---:|---:|---:|
+| 원본 공유 | 4.070 | 5.05 | 167.22 | 7.34 |
+| 항상 복사 | 0.137 | 1.56 | 168.36 | 7.86 |
+| 크기 비율 2 기준 | 0.137 | 1.56 | 167.15 | 7.86 |
+| 메모리 압력 기준 | 4.070 | 5.50 | 167.12 | 7.34 |
+| 필요한 부분만 읽기 | 0.074 | 1.52 | 167.51 | 7.86 |
+
+공유의 완료 시간 범위는 167.16~169.33 ms, 복사는 166.68~177.07 ms였다.
+이 표로 작은 시간 차이의 우열이나 동일한 서비스 SLO 충족을 주장하지 않는다.
+여기서 분명한 관찰은 **작은 결과를 독립시키면 유지해야 하는 원본 공간이 줄어든다**는 것이다.
+
+항상 복사해도 전체 명시적 복사는 128 KiB였다. 원본 입력 약 128 MiB 전체를 복사한 것이 아니다.
+형식이 허용한 선별 읽기는 그 복사도 없앨 수 있었다. 다만 read/write 등 다른 데이터 이동은 여전히 있다.
+
+크기 관계로도 설명할 수 있다. 대략 Q개 결과가 남아 있을 때 원본 공유는 `Q × 원본 용량`,
+복사는 `Q × 선택 용량 + 처리 중인 원본 용량`을 요구한다. 실제 측정에는 관리 공간·캐시·할당 반올림이 더해진다.
+이 비용 관계를 Pure의 새 발견으로 취급하지 않는다.
+
+## 결과 2: 메모리만 줄이면 큐 대기는 커질 수 있다
+
+큐 한도를 512개로 둔 같은 작은 선택 조건에서, 공유는 예산 때문에 최대 126개를 보관했고
+복사는 512개까지 보관했다. 최대 관리 공간은 공유 7.963 MiB에서 복사 0.188 MiB로 줄었지만,
+프로세스 내부 P99 상한 중앙값은 약 9.44 ms에서 41.94 ms로 커졌다.
+
+그만큼 작업이 큐 안에서 기다렸다. 입력을 아직 읽지 않아 밖에서 대기한 시간은 이 지연에 포함되지 않는다.
+따라서 공유 방식의 서비스 전체 지연이 더 짧다고 해석할 수도 없다.
+**메모리 예산·큐 길이·외부 도착률을 함께 정해야 지연에 관한 비교가 가능하다.**
+
+이 관찰 후 추가한 것이 앞의 64개 큐 검사다. 사후에 추가한 탐색 실험임을
+[v2 계획](../../research/experiments/002-buffer-retention/plan.json)에 명시했다.
+
+## 결과 3: 복사를 많이 하면 항상 좋은 것도 아니다
+
+64 KiB 중 48 KiB를 남기는 조건에서는 이번 할당기의 용량 반올림 때문에 선택 버퍼도 64 KiB가 된다.
+공유와 복사 모두 최대 관리 공간이 약 7.963 MiB였고, 항상 복사 방식은 추가로 96 MiB를 복사했다.
+크기 기준 정책은 이 불필요한 복사를 하지 않았다.
+
+즉, 원본과 선택 데이터의 관계뿐 아니라 **실제 할당 용량**을 고려해야 한다.
+다른 용량 구분·정확한 크기 할당기에서는 결과가 달라질 수 있다. 이번 작은 풀을 최선의 할당기라고 주장하지 않는다.
+
+메모리 부족이 없는 빠른 소비자 조건에서는 대부분의 방식이 약 0.149 MiB만 관리했고,
+전체 완료 시간 중앙값도 약 20~23 ms 범위였다. 느린 소비자에서 관찰한 큰 유지량 차이를
+모든 실행에 일반화할 수 없다.
+
+## 결과 4: 좁은 예산에서는 원본 유지가 진행 자체를 늦출 수 있다
+
+4~256 KiB 원본과 다양한 선택 비율이 섞인 입력을 2 MiB 예산으로 처리했다.
+모든 방식이 거의 같은 최대 관리 공간까지 사용한다. 이 경우에는 완료 시간과 복사량이 달라졌다.
+
+| 방식 | 완료 시간 중앙값 ms | 측정 범위 ms | 명시적 복사 MiB |
+|---|---:|---:|---:|
+| 원본 공유 | 493.63 | 491.78~495.15 | 0 |
+| 항상 복사 | 191.25 | 191.04~191.77 | 51.24 |
+| 크기 비율 2 기준 | 188.82 | 188.31~201.97 | 27.16 |
+| 크기 비율 4 기준 | 225.45 | 224.73~229.05 | 10.71 |
+| 크기 비율 8 기준 | 283.68 | 282.26~289.85 | 3.65 |
+| 메모리 압력 기준 | 190.69 | 189.79~193.71 | 27.07 |
+| 필요한 부분만 읽기 | 175.31 | 174.67~179.28 | 0 |
+
+명시적 복사 바이트가 적다고 완료가 빠르지는 않았다. 원본을 오래 보관하면 다음 입력을 받아들일
+공간이 부족해지고, 합성 보관 시간을 기다리는 동안 작업 진행이 제한된다.
+압력 기준 정책은 단순한 크기 비율 2 정책과 비슷한 결과였다. 후보 정책의 고유한 이점은 확인하지 못했다.
+
+이 결과는 해당 로컬 파이프라인의 처리 시간이다. 약 2.6배의 시간 차이를
+서버·파서·Pure 전체의 성능 개선으로 표기하지 않는다.
+
+버퍼 재사용의 효과도 별도로 확인했다. 64개 큐의 작은 선택 조건에서 복사 방식의 실제 할당 호출은
+캐시를 끄면 4,096회, 켜면 65회였지만 완료 시간의 뚜렷한 개선은 확인하지 못했다.
+할당 횟수 감소와 사용자 체감 성능 개선은 서로 다른 주장이다.
+
+## 계측 중 발견하고 수정한 문제
+
+첫 실행에서 모든 RSS가 약 27.42 MiB로 동일했다. 이를 결과로 확정하지 않고 조사했다.
+Linux `getrusage`의 자원 통계는 exec를 지나서도 보존되므로, 이 실행 경로의 값에는
+프로그램 실행 전 이력이 섞여 있었다.
+[getrusage 문서](https://man7.org/linux/man-pages/man2/getrusage.2.html).
+
+현재 결과는 Linux `/proc/self/status`의 현재 메모리 이미지 `VmHWM`을 별도로 읽고,
+원래 `rusage`도 구분하여 남긴다. Linux 문서는 이 RSS 계측도 근삿값이라고 설명한다.
+정확한 요청 바이트 계수와 동일한 지표가 아니다.
+[proc status 문서](https://man7.org/linux/man-pages/man5/proc_pid_status.5.html).
+
+[첫 실행 기록](../../research/experiments/002-buffer-retention/results/2026-09-22-linux-aarch64/audit.json)과
+당시 소스는 보존했다. 본문의 수치는 모두 수정된 v2 실행에서 가져왔다.
+수정 후 큰 원본 유지와 작은 선택 결과가 RSS에서도 구분되는지 회귀 검사를 추가했다.
+
+## 이 실험이 아직 다루지 않은 것
+
+- 하나의 원본을 여러 소비자가 공유하는 분기, 영속 캐시, 독립적인 여러 처리 단계의 예산 조정.
+- Sonic·Node.js·.NET·Arrow 자체의 현재 구현과 직접 비교.
+- 일반 JSON·문서·압축 파서와 외부 라이브러리 내부 메모리.
+- 독립적인 요청 도착, 서비스 전체 지연, 네트워크·GPU·비동기 취소 경합.
+- 온도·전력 통제, 별도 계측 오버헤드 보정, Mac 실행, 통계적 유의성 검정.
+- 사용자에게 필요한 실제 작업의 메모리·지연 목표와 새 도구의 개발 경험.
+
+따라서 006의 여러 단계에 걸친 가설 전체를 기각한 것은 아니다.
+**단일 입력·단일 소비자에서의 공유/복사 선택만으로는 Pure의 필요성을 입증하지 못했다**는 결론이다.
+
+## 다음 설계 결정
+
+1. 무복사나 전용 할당기 자체를 대표 차별점으로 삼지 않는다. 기존 크기 기준 정책을 필수 비교 대상으로 유지한다.
+2. [버퍼 수명 초안](../design/002-memory-lifetime.md)에는 필요한 계측 질문이 생겼다.
+ 원본 할당과 선택 범위, 마지막 소비자, 빈 풀의 유지량, 큐 밖의 대기까지 구분해야 한다.
+ 이 실험만으로 현재 계약의 비동기 보장이 구현·검증된 것은 아니다.
+3. 다음 후보 검증은 실제 파서와 여러 소비자가 같은 원본을 공유하는 작업으로 한정한다.
+ 그 작업의 기존 구현에서도 남는 비용이 확인될 때 전역 수명·예산 정책과 선행연구 비교를 진행한다.
+ 같은 해법으로 충분하면 이 주제의 차별화 우선순위를 낮추고 증분 편집 후보로 이동한다.
+
+## 재현·원자료
+
+- [비교 코드](../../research/experiments/002-buffer-retention/benchmark.c), [실행·검증 도구](../../research/experiments/002-buffer-retention/run.py)
+- [실행 환경·빌드·소스 해시](../../research/experiments/002-buffer-retention/results/2026-09-22-linux-aarch64-v2/environment.json)
+- [입력·출력 해시](../../research/experiments/002-buffer-retention/results/2026-09-22-linux-aarch64-v2/fixtures.json)
+- [검증 기록](../../research/experiments/002-buffer-retention/results/2026-09-22-linux-aarch64-v2/verification.json)
+- [240개 원자료](../../research/experiments/002-buffer-retention/results/2026-09-22-linux-aarch64-v2/raw.csv), [중앙값·범위](../../research/experiments/002-buffer-retention/results/2026-09-22-linux-aarch64-v2/summary.json)
+
+```sh
+python3 research/experiments/002-buffer-retention/run.py --check-only
+python3 research/experiments/002-buffer-retention/run.py --output /tmp/pure-retention-results
+```
+
+출력 경로는 새 디렉터리여야 한다. 측정 도구는 기존 결과를 덮어쓰지 않는다.
+
+2026-09-22 후속: [008](008-json-fanout-results.md)에서 실제 JSON 파서와 최대 세 소비자로
+확장했다. 그 범위에서도 기존 복사·복사본 공유 정책이 효과적이었으며, 대표 연구 문제로서의
+우선순위를 낮추고 증분 편집·상태 갱신 후보로 이동하기로 했다.