diff options
Diffstat (limited to 'docs/research/007-buffer-retention-results.md')
| -rw-r--r-- | docs/research/007-buffer-retention-results.md | 195 |
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 파서와 최대 세 소비자로 +확장했다. 그 범위에서도 기존 복사·복사본 공유 정책이 효과적이었으며, 대표 연구 문제로서의 +우선순위를 낮추고 증분 편집·상태 갱신 후보로 이동하기로 했다. |
