@hatiolab/figure-model 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +3 -0
- package/dist/asset.d.ts +12 -0
- package/dist/asset.d.ts.map +1 -1
- package/dist/asset.js +15 -3
- package/dist/asset.js.map +1 -1
- package/dist/command-preview.d.ts +6 -0
- package/dist/command-preview.d.ts.map +1 -1
- package/dist/command-preview.js +35 -2
- package/dist/command-preview.js.map +1 -1
- package/dist/editor-workspace.d.ts +48 -401
- package/dist/editor-workspace.d.ts.map +1 -1
- package/dist/editor-workspace.js +55 -13
- package/dist/editor-workspace.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel-version.d.ts +1 -1
- package/dist/kernel-version.js +1 -1
- package/dist/names.d.ts +19 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +47 -0
- package/dist/names.js.map +1 -0
- package/dist/package.d.ts +86 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +202 -0
- package/dist/package.js.map +1 -0
- package/dist/render-lod.d.ts +12 -0
- package/dist/render-lod.d.ts.map +1 -0
- package/dist/render-lod.js +32 -0
- package/dist/render-lod.js.map +1 -0
- package/dist/shape-ops.d.ts +1 -1
- package/dist/shape-ops.d.ts.map +1 -1
- package/dist/shape-ops.js.map +1 -1
- package/dist/words.d.ts +6 -6
- package/dist/words.js +8 -8
- package/dist/words.js.map +1 -1
- package/dist/work-counter.d.ts +16 -0
- package/dist/work-counter.d.ts.map +1 -0
- package/dist/work-counter.js +13 -0
- package/dist/work-counter.js.map +1 -0
- package/docs/evaluation-performance-summary-2026-10-05.md +18 -0
- package/docs/performance-followup-2026-10-05.md +578 -0
- package/docs/shape-dimension-contract.md +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
# Figure 후속 성능 실행 계획 (2026-10-05)
|
|
2
|
+
|
|
3
|
+
결정 정본: [ADR-0108 현행 보완](../../operato-application/adr/ADR-0108.md).
|
|
4
|
+
출발점: 0.3.4·10.1.73 일반 변경분 채택 및 설치본 검증 완료. 과거 튜닝 성과는 유지한다.
|
|
5
|
+
제품 목표와 원자료는 [성능 요약](evaluation-performance-summary-2026-10-05.md)을 따른다.
|
|
6
|
+
이 문서는 실행 상태를 관리하며 예정 효과를 성과로 기록하지 않는다.
|
|
7
|
+
|
|
8
|
+
## 순서와 현재 상태
|
|
9
|
+
|
|
10
|
+
| 순서 | 후보 | 먼저 확인할 근거 | 채택 조건 | 현재 상태 |
|
|
11
|
+
| ---- | ------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------- |
|
|
12
|
+
| P1 | 내부 임시 객체·배열의 할당 감소 | kernel/changes buffer/rig/geometry allocation stack, bytes/frame와 GC | 큰 할당 출처 한 곳의 개선, 비계측 전체 프레임·p95와 소유권 유지 | 호출 트리 집계 완료; 첫 대상 = 병합 은행 형상 키 |
|
|
13
|
+
| P2 | 반복 부품 공통 계산 | 반복마다 같은 변환·id/path·layout을 재구성하는 양 | 동일 연산 순서·bit 동일과 구조 변경 지원 | 첫 후보 측정; 기본 채택 보류 |
|
|
14
|
+
| P3 | 동일 입력 평가 공유 | 동일/서로 다른 size·pose에서 중복량 | 모든 의존성·정의/버전 포함, 불변 공유와 인스턴스 격리, bounded cache | 검토 예정 |
|
|
15
|
+
| P4 | resize 이벤트 프레임 단위 반영 | 이벤트 수 대비 실제 update 수, 제품 drag trace | 부착/시뮬레이션·최종 입력 정확성, 조회 시 최신성 | 중복 존재 확인 후 판단 |
|
|
16
|
+
| P5 | 실행 계획 간소화 | 상수 의존 노드·미사용 중간 출력·실행 횟수 | 공개 값 조회/진단 보존, 전체 평가 동등성, 컴파일·메모리 비용 포함 | 검토 예정 |
|
|
17
|
+
|
|
18
|
+
P2 코드의 단서: `repeat@1`은 매 평가마다 path/id, identity 행렬과 여러 compose 결과를 만든다.
|
|
19
|
+
변경분은 structure 여부가 결정되기 전에도 ids/keys/paths/indexes를 모으며 path 동결 사본을 구성한다.
|
|
20
|
+
이는 읽기에서 찾은 후보이지 최대 병목 또는 제거 가능하다는 확정은 아니다. 프로필로 순서를 정한다.
|
|
21
|
+
|
|
22
|
+
## P1 측정 설계
|
|
23
|
+
|
|
24
|
+
- 기존 `check-figure-gap.mjs`에 `ALLOCATION_PROFILE=1`을 추가해 collected object를 포함한 sampled heap
|
|
25
|
+
allocation stack 원자료와 할당 위치별 bytes/frame을 보존한다. profiling과 frame timing 구간은 분리한다.
|
|
26
|
+
- 첫 실행은 한 판의 동일 빌드로 board 60대·크기만 교대, 1 round의 D1/D2/C다. 이름 D/C는 기존 harness 호환용이며
|
|
27
|
+
**이번에는 모두 동일 baseline**이다. 이것으로 성능 개선을 주장하지 않는다.
|
|
28
|
+
- 원자료에 GPU, build 경로, 입력, 계측 조건을 남긴다. minified 위치는 sourcemap 또는 함수/호출 사슬로 확인한다.
|
|
29
|
+
익명 노드를 임의로 커널에 귀속하지 않는다. sampled bytes는 추정량이고 총 할당의 정확한 계수가 아니다.
|
|
30
|
+
- 프로필로 가장 큰 할당 출처를 고른 뒤 같은 입력의 500대에서 확인한다. 동일 입력과 인스턴스별 상이 입력을 따로 검증한다.
|
|
31
|
+
- 구현 후보마다 회귀 시험→수정→관련 시험·build→비계측 browser A/A·A/B 순서로 진행한다.
|
|
32
|
+
반복 수와 순서는 사전에 고정한다. 중앙값·p95, 생성·보유/peak/dispose 메모리, draw·triangles·body·copies를 보고한다.
|
|
33
|
+
- 전체 스냅샷 미조회/가끔 조회/매 프레임 조회, resize 뒤 pose, flag 및 부착점도 확인한다.
|
|
34
|
+
|
|
35
|
+
## 제약과 중단 기준
|
|
36
|
+
|
|
37
|
+
- 내부 작업 버퍼는 재사용 가능하지만 호출자 소유 typed array와 이전 snapshot을 덮어쓰지 않는다.
|
|
38
|
+
- 읽기 수명 제한만으로 내부 가변 객체 노출을 허용하지 않는다. 전체 `plan.evaluate`의 가변 소유권도 유지한다.
|
|
39
|
+
- 기능 생략, 동일 시간 강제, 출력 clamp로 측정량을 줄이지 않는다. metadata·id·path·selection을 보존한다.
|
|
40
|
+
- 후보 하나당 먼저 고정한 A/B 실행을 마친 뒤 판단한다. 효과가 관찰 변동과 구별되지 않으면 보류하고 다음 큰 비용으로 이동한다.
|
|
41
|
+
- 이미 다른 세션에서 구현한 LOD(ADR-0116)는 별도다. baseline은 OFF이고 이번 작업은 해당 runtime 소스를 변경하지 않는다.
|
|
42
|
+
- 패키지 발행·제품 의존성 갱신은 별도 결정이다. 다른 세션의 worktree/미커밋/index를 변경하지 않는다.
|
|
43
|
+
|
|
44
|
+
## 기록
|
|
45
|
+
|
|
46
|
+
2026-10-05: ADR-0108 일반 변경분 현행 계약과 P1~P5를 보완했고 ADR-0109·0111의 초기 현황에
|
|
47
|
+
후속 발행/설치 검증 링크를 추가했다. 런타임 최적화는 아직 적용하지 않았다.
|
|
48
|
+
|
|
49
|
+
### P1 첫 관찰 (60대, 동일 baseline 3회)
|
|
50
|
+
|
|
51
|
+
Apple M5 Metal, board, 폭 2400↔2496만 교대, warmup 30/frame 60.
|
|
52
|
+
기존 gap harness의 D1/D2/C를 모두 같은 things-scene.mjs에 연결했다.
|
|
53
|
+
heap profiler는 4,096 byte 간격으로 collected object를 포함하고 각 60프레임의 할당을 샘플링한다.
|
|
54
|
+
총 sampled allocation: 10.21 / 10.49 / 10.49 MiB/frame.
|
|
55
|
+
|
|
56
|
+
| 대표 할당 위치 | 위치별 sampled KiB/frame (3회) | 해석 |
|
|
57
|
+
| --------------- | ------------------------------ | -------------------------------------------------------------------------------- |
|
|
58
|
+
| fromChanges | 933 / 948 / 940 | rig의 body·중첩 pose 재구성과 외형 입히기 후보 |
|
|
59
|
+
| planOf → wA | 928 / 895 / 914 | 배치 계획 호출 사슬. minified 함수 wA를 source로 매핑한 후 대상 확정 |
|
|
60
|
+
| advance → l → A | 804 / 801 / 801 | kernel 일반 변경 처리 호출 사슬. 익명/압축 이름을 정확히 귀속하기 전 최적화 금지 |
|
|
61
|
+
|
|
62
|
+
이는 상위 selfSize 위치 일부이며 각 함수 전체의 inclusive allocation 또는 정확한 byte 계수가 아니다.
|
|
63
|
+
대부분의 할당을 위 세 곳이 모두 설명한다고 주장하지 않는다.
|
|
64
|
+
|
|
65
|
+
`fromChanges` source를 확인하면 각 body를 새 record로 만들고 pose의 중첩 배열을 구성한 뒤
|
|
66
|
+
`dressOne`에서 다시 spread해 dressed record를 만든다. bindings Map와 palette 사본도 만든다.
|
|
67
|
+
후보는 가변 renderer 내부 전용 body/pose의 재사용과 중간 body wrapper 제거다.
|
|
68
|
+
다만 이전 `lastDrawn` 보관·LOD 경계 캐시·스냅샷 조회·실패 중간 상태·plan 동일성에 대한 계약부터
|
|
69
|
+
확인해야 한다. 현재 LOD 작업과 겹치는 rig 소스는 즉시 수정하지 않는다.
|
|
70
|
+
|
|
71
|
+
원자료: things-scene/test/browser/figure-vs-js/gap-1791199515874.json 및 같은 디렉터리 allocation-*.json.
|
|
72
|
+
생성 원자료는 기존 ignore 정책대로 로컬에 남기고 요약을 이 문서에 보존한다.
|
|
73
|
+
|
|
74
|
+
### P1 규모 확인 (500대, 동일 baseline 3회)
|
|
75
|
+
|
|
76
|
+
같은 board/입력/계측 조건에서 77.22 / 76.97 / 76.93 MiB/frame이었다.
|
|
77
|
+
비계측 frame 중앙값은 49.67 / 51.65 / 50.01ms였다. 세 표식은 같은 빌드이므로 성능 개선을 뜻하지 않는다.
|
|
78
|
+
대표 self allocation 위치는 fromChanges 약 7.7MiB/frame, planOf→wA 약 7.3MiB/frame,
|
|
79
|
+
커널 advance→l→A 약 6.5MiB/frame이었다(두 번째 실행). 60대의 호출 사슬과 일치한다.
|
|
80
|
+
원자료: things-scene/test/browser/figure-vs-js/gap-1791199606515.json 및 allocation-board-500-*.json.
|
|
81
|
+
|
|
82
|
+
첫 구현 후보는 fromChanges의 중간 body wrapper·중첩 pose 객체 할당을 줄이는 것으로 특정한다(잠정 — 아래 「첫 대상 다시 정하기」에서 바꿨다).
|
|
83
|
+
적용 전에 lastDrawn 보관, LOD revision/bounds cache, shape/body 동일성, 공개 evaluation의 동결 및 실패 시
|
|
84
|
+
그림 유지 계약을 시험으로 고정한다. 전체 가변 객체 재사용보다 중간 wrapper 제거를 먼저 비교한다.
|
|
85
|
+
공개 소유권을 바꾸는 재사용은 허용하지 않는다. rig 소스는 다른 세션 LOD 변경이 있으므로 별도 변경 단위로 진행한다.
|
|
86
|
+
아직 runtime 후보는 적용하지 않았다. 이번 완료 범위는 ADR 현행화·실행 계획·profile 도구·기준 분석이다.
|
|
87
|
+
|
|
88
|
+
### P1 호출 트리 집계 (기존 원자료 6개, 새 측정 없음)
|
|
89
|
+
|
|
90
|
+
원자료: things-scene/test/browser/figure-vs-js/allocation-board-{60,500}-{D1,D2,C}-r1-*.json(세 표식 모두 같은 빌드). 각 할당(sampled
|
|
91
|
+
selfSize)을 호출 사슬의 잎에서 뿌리 쪽으로 올라가며 처음 만나는 기준 프레임 하나에만 귀속했다. 기준 프레임은 압축 뒤에도 이름이
|
|
92
|
+
남는 메서드다. 커널 `advance`·`read`·`changed`·`value`·`place`, rig 외형 `fromChanges`·`dressOne`·`dress`, 그리기 준비 `draw`·`planOf`·
|
|
93
|
+
`drawMerged`·`drawCarried`, 보드·렌더러 `onchange`·`_applyProps`·`set`·`trigger`·`renderThreeD`·`render`. rig `update` 바로 아래에서 어느
|
|
94
|
+
기준에도 닿지 않은 것과 기준이 없는 것은 미분류다. 값은 MiB/frame(비중), 합계는 전체 sampled bytes와 같다.
|
|
95
|
+
|
|
96
|
+
중복 없는 분류:
|
|
97
|
+
|
|
98
|
+
| 분류 | 60대 D1 / D2 / C | 500대 D1 / D2 / C |
|
|
99
|
+
| ------------ | ----------------------------------------- | -------------------------------------------- |
|
|
100
|
+
| 커널 | 4.92 (48.2%) / 4.92 (46.9%) / 4.94 (47.1%) | 41.01 (53.1%) / 41.00 (53.3%) / 40.94 (53.2%) |
|
|
101
|
+
| 그리기 준비 | 3.70 (36.3%) / 3.96 (37.8%) / 3.95 (37.7%) | 23.26 (30.1%) / 23.05 (30.0%) / 23.04 (30.0%) |
|
|
102
|
+
| rig 외형 | 0.94 (9.2%) / 0.95 (9.1%) / 0.95 (9.0%) | 7.94 (10.3%) / 7.89 (10.2%) / 7.94 (10.3%) |
|
|
103
|
+
| 보드·렌더러 | 0.61 (6.0%) / 0.61 (5.8%) / 0.62 (5.9%) | 4.85 (6.3%) / 4.88 (6.3%) / 4.86 (6.3%) |
|
|
104
|
+
| 미분류 | 0.04 (0.3%) / 0.04 (0.3%) / 0.04 (0.3%) | 0.16 (0.2%) / 0.15 (0.2%) / 0.15 (0.2%) |
|
|
105
|
+
| 합계 | 10.21 / 10.49 / 10.49 | 77.22 / 76.97 / 76.93 |
|
|
106
|
+
|
|
107
|
+
분류 안의 inclusive 순위(부모와 자식은 합산하지 않는다. 들여쓴 줄은 바로 위 줄 안에 포함된다). 압축 이름은 번들 원문의 그 위치
|
|
108
|
+
코드로 정체를 확인한 것만 적었다.
|
|
109
|
+
|
|
110
|
+
| 분류·호출 | 60대 D1 / D2 / C | 500대 D1 / D2 / C |
|
|
111
|
+
| ---------------------------------------------------------- | ------------------ | ------------------- |
|
|
112
|
+
| 커널 `advance` | 분류 합계와 같음 | 41.01 / 41.00 / 40.94 |
|
|
113
|
+
| `step`(압축 `A`) | 4.65 / 4.65 / 4.68 | 38.93 / 38.91 / 38.85 |
|
|
114
|
+
| 행 `repeat@1` 실행 | 1.17 / 1.16 / 1.16 | 9.76 / 9.78 / 9.71 |
|
|
115
|
+
| 밀어내기 `extrude@1` 실행 → `runExtrude`(압축 `Hw`) | 1.11 / 1.12 / 1.13 | 9.32 / 9.31 / 9.33 |
|
|
116
|
+
| 밀어내기 매개변수 해석(압축 `Tw`) | 0.41 / 0.41 / 0.41 | 3.42 / 3.41 / 3.44 |
|
|
117
|
+
| 형상 description 생성(압축 `Iw`) | 0.55 / 0.55 / 0.56 | 4.66 / 4.67 / 4.67 |
|
|
118
|
+
| `rigid@1` 실행 | 0.70 / 0.69 / 0.70 | 5.82 / 5.86 / 5.82 |
|
|
119
|
+
| 행렬곱 `mm`(압축 `fM`, 여러 연산이 부름) | 0.62 / 0.60 / 0.61 | 5.09 / 5.11 / 5.06 |
|
|
120
|
+
| 그리기 준비 `drawMerged` | 2.34 / 2.63 / 2.60 | 12.94 / 12.81 / 12.77 |
|
|
121
|
+
| 병합 은행 조회 `acquireMergedFigureGeometry`(압축 `rk`) | 2.22 / 2.51 / 2.48 | 12.21 / 12.08 / 12.05 |
|
|
122
|
+
| 부품 형상 문자열 키 `figureGeometryKeyOf`의 map·join | 1.03 / 1.05 / 1.04 | 8.61 / 8.67 / 8.60 |
|
|
123
|
+
| 그리기 준비 `planOf` | 1.06 / 1.03 / 1.05 | 8.44 / 8.37 / 8.38 |
|
|
124
|
+
| `figureRenderBatchesOf`(압축 `wA`, body별 signature 포함) | 0.97 / 0.94 / 0.96 | 7.93 / 7.85 / 7.88 |
|
|
125
|
+
| rig 외형 `fromChanges` | 0.94 / 0.95 / 0.95 | 7.94 / 7.89 / 7.94 |
|
|
126
|
+
| 보드 컴포넌트 `set`(내장) | 0.30 / 0.31 / 0.31 | 2.49 / 2.49 / 2.49 |
|
|
127
|
+
| 보드 `WebGLRenderer.render` | 0.13 / 0.12 / 0.13 | 0.99 / 1.01 / 1.00 |
|
|
128
|
+
|
|
129
|
+
미분류는 프레임 시계 `tick`의 Set 등과 rig `update`의 입력 준비(`stateOverrides`)다(500대 0.15~0.16 MiB/frame).
|
|
130
|
+
|
|
131
|
+
### 첫 대상 다시 정하기
|
|
132
|
+
|
|
133
|
+
| 후보 | 할당(500대) | 제거 가능한 몫(코드 읽기 추정) | 직접 CPU 근거 | 계약 위험 |
|
|
134
|
+
| ---------------------------------------------------- | ---------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- | --------- |
|
|
135
|
+
| 병합 은행 조회의 부품 형상 키 | 8.6 MiB (11%) | 크기 변경에서 형상이 그대로인 부품(16개 중 11개)의 키 | 은행 조회가 rig당 6~8 µs(Node, 형상 키 4.5 µs), 500대 `drawMerged` 5.5 ms | 낮음(동결 형상만 캐시, things-scene 안) |
|
|
136
|
+
| 배치 계획의 body signature(`figureRenderBatchesOf`) | 7.9 MiB (10%) | 그대로인 형상의 signature | 500대 `planOf` 5.5 ms | 낮음(figure-model 안, 같은 규칙) |
|
|
137
|
+
| 밀어내기 매개변수 해석 | 3.4 MiB (4%) | 노드의 고정 매개변수에만 의존해 거의 전부 | 따로 재지 않음 | 매우 낮음 |
|
|
138
|
+
| 행 `repeat@1` 실행 | 9.8 MiB (13%) | 사본 id·path·offset 일부(P2) | 따로 재지 않음 | 중간(id·path·선택 의미) |
|
|
139
|
+
| rig 외형 `fromChanges` | 7.9 MiB (10%) | 중간 body wrapper·중첩 자세의 일부 | 500대 1.75 ms | 중간(lastDrawn·LOD·실패 시 유지) |
|
|
140
|
+
| 밀어내기 형상 생성(`runExtrude` 본체) | 9.3 MiB (12%) | 낮음(새 크기의 형상은 새로 만들어야 한다) | — | — |
|
|
141
|
+
|
|
142
|
+
- 첫 대상을 `fromChanges`에서 병합 은행 조회의 부품 형상 키로 바꾼다. 할당은 `fromChanges`보다 약간 크고, 제거 가능한 몫이 분명하며,
|
|
143
|
+
직접 CPU 근거가 있고, 계약 위험이 낮다. 두 번째는 같은 규칙의 배치 계획 signature, 세 번째는 밀어내기 매개변수 해석이다.
|
|
144
|
+
- 이 두 키 캐시는 2026-10-05에 한 번 구현해 되돌린 후보다(형상별 키 캐시: 배치 계획 약 0.2 ms 감소, 전체 프레임 효과 미확인,
|
|
145
|
+
things-scene `docs/plans`와 completeness review 기록). 그때 남긴 시험(`things-scene/test/figure-geometry-key.check.js`,
|
|
146
|
+
figure-model `render-batches.test.ts`)을 출발점으로 쓴다. 당시 판단은 60대 위주의 페이지 교차 측정이었다. 이번에는 500대
|
|
147
|
+
실행 단위 A/B로 판단하고, 효과가 변동과 구별되지 않으면 다시 보류한다.
|
|
148
|
+
- 할당 감소가 GC 시간에 비례한다고 가정하지 않는다. 채택은 비계측 전체 프레임 중앙값·p95와 메모리로 판단한다.
|
|
149
|
+
- 행 실행과 `fromChanges`는 그 뒤에 다시 고른다.
|
|
150
|
+
|
|
151
|
+
### P1 첫 대상: 병합 은행의 부품 형상 키 캐시 — 채택하지 않음 (2026-10-05)
|
|
152
|
+
|
|
153
|
+
things-scene `candidate/merged-shape-key`(병합하지 않음). 깊게 동결된 형상만 형상 객체마다 segments별 키를 `WeakMap`에 둔다. 가변
|
|
154
|
+
형상과 겉만 동결된 형상은 매번 계산한다. 시험: 키 시험에 LOD 단계(segments) 전환을 더했고, figure 시험 92개와 단위 시험 2,713개가
|
|
155
|
+
통과했다. 모든 형상 캐시, 겉만 동결 검사, segments 무시 결함을 심어 실패를 확인했다.
|
|
156
|
+
|
|
157
|
+
측정(`check-figure-gap.mjs`, 보드, 크기만 교대, 사전 고정 6 round × D1·D2·C, 순서 여섯 가지 한 번씩, 워밍업 30, 비계측 150프레임).
|
|
158
|
+
D는 v10 `f287f5f7` 빌드, C는 그 위에 이 후보만 얹은 빌드다.
|
|
159
|
+
|
|
160
|
+
| 500대 | A/A (D2−D1) round별 | 후보 (C−D1) round별 |
|
|
161
|
+
| ---------------------------- | -------------------------------- | ------------------------------ |
|
|
162
|
+
| 프레임 중앙값 차이 ms | −0.35 −0.09 −0.62 −0.46 +0.42 −0.10 | +0.57 +0.30 +1.33 +1.68 +0.70 +0.53 |
|
|
163
|
+
| p95 차이 ms | −0.36 −2.06 −2.02 −0.55 +0.49 −0.22 | +3.09 +6.74 +7.10 +6.25 +5.83 +6.87 |
|
|
164
|
+
|
|
165
|
+
| 500대 | D1 | D2 | C |
|
|
166
|
+
| ------------------------- | ------------------- | ------------------- | -------------------- |
|
|
167
|
+
| 할당 MB/frame(6회) | 76.75~77.26 | 76.92~77.39 | 72.61~73.05 |
|
|
168
|
+
| 보유 MB(측정 뒤 GC) | 42.7~43.3 | 42.7~43.3 | 46.9 |
|
|
169
|
+
| 단계 계측 merged / GC ms | 5.49 / 4.72 | 5.80 / 4.51 | 5.63 / 5.36 |
|
|
170
|
+
|
|
171
|
+
60대(회귀 확인, 2 round): 프레임 차이 −0.63, −0.08 ms(A/A −2.97, +0.22), 할당 10.5 → 10.0 MB/frame, 보유 10.1 → 10.7 MB. 작업량(draw call,
|
|
172
|
+
삼각형, body, 사본)은 두 판이 같았다.
|
|
173
|
+
|
|
174
|
+
판정: 채택하지 않는다. 할당은 프레임당 약 4.2 MB(5%) 줄었지만, 500대에서 전체 프레임 중앙값이 6 round 모두 0.3~1.7 ms, p95가 3~7 ms
|
|
175
|
+
나빠졌고 보유 메모리가 3.6 MB 늘었다. 병합 단계 시간은 줄지 않았다. 크기 변경마다 새로 생기는 형상 객체가 짧게 사는 WeakMap 키로
|
|
176
|
+
쌓여 GC 처리가 늘었다는 해석은 GC 시간·보유량 증가와 맞지만 직접 확인하지 않았다.
|
|
177
|
+
|
|
178
|
+
다음: 지시대로 상수 node parameter 파싱(밀어내기 매개변수 해석, 500대 3.4 MiB)으로 넘어간다.
|
|
179
|
+
|
|
180
|
+
### P1 두 번째: 상수 node parameter 파싱 — 효과 미확인, 보류 (2026-10-05)
|
|
181
|
+
|
|
182
|
+
figure-model `candidate/extrude-param-cache`(병합하지 않음). 컴파일된 plan이 자기 사본의 node 매개변수를 동결하고, `extrude@1`이 실행
|
|
183
|
+
중에 읽는 레이아웃과 description을 동결된 매개변수 객체마다 한 번만 만든다. 호출자 문서의 매개변수는 동결하지 않고, 동결되지 않은
|
|
184
|
+
매개변수는 매번 읽는다. 결과에는 그 객체가 들어가지 않는다(숫자와 이름만 복사). 시험 `src/shape-params.test.ts` 3개, 결함 3종(동결 여부
|
|
185
|
+
무시, 결과 faces 공유, 호출자 매개변수 동결)을 심어 실패를 확인했고 기존 759개가 통과했다(figure-model HEAD는 다른 세션의
|
|
186
|
+
`src/render-lod.test.ts` 타입 오류 하나로 `npm test`가 타입 검사에서 멈춰 있어 `node --test`로 직접 돌렸다. 정상 `npm test`·build 검증은
|
|
187
|
+
그 오류가 고쳐진 뒤에 다시 한다 — 직접 실행한 통과만으로 전체 검증 완료가 아니다). → 차단 해제: 아래 「타입 수정 뒤 정상 시험·build 검증」(762개 통과, build 성공).
|
|
188
|
+
|
|
189
|
+
측정은 첫 후보와 같다(보드, 크기만 교대, 6 round × D1·D2·C, 순서 여섯 가지, 워밍업 30, 비계측 150프레임). things-scene은 같은 `f287f5f7`이고
|
|
190
|
+
D는 figure-model HEAD `83737e3`, C는 그 위에 이 후보만 얹었다.
|
|
191
|
+
|
|
192
|
+
| 500대 | A/A (D2−D1) round별 | 후보 (C−D1) round별 |
|
|
193
|
+
| ---------------------- | -------------------------------------- | --------------------------------------- |
|
|
194
|
+
| 프레임 중앙값 차이 ms | +0.02 −0.20 −1.38 −1.07 +0.93 +0.54 | −0.76 −1.31 −1.07 −1.20 +0.51 −1.32 |
|
|
195
|
+
| p95 차이 ms | −1.10 +0.92 −2.44 −1.10 +1.92 −0.78 | −2.78 −2.49 −1.79 +1.28 +1.61 −1.48 |
|
|
196
|
+
|
|
197
|
+
| 500대 | D1 | D2 | C |
|
|
198
|
+
| ----------------------------- | ------------ | ------------ | ------------ |
|
|
199
|
+
| 할당 MB/frame(6회) | 76.84~77.19 | 76.86~77.31 | 70.51~71.12 |
|
|
200
|
+
| 보유 MB(측정 뒤 GC) | 42.7~43.3 | 42.7~43.3 | 43.3 |
|
|
201
|
+
| 단계 계측 커널 / GC ms | 20.08 / 4.71 | 19.78 / 4.74 | 18.79 / 4.51 |
|
|
202
|
+
|
|
203
|
+
60대(회귀 확인, 2 round): 프레임 차이 +0.94, +0.25 ms(A/A −0.05, −0.23), 커널 2.51 → 2.44 ms, 할당 10.8 → 10.0 MB/frame, 보유 같음. 작업량은
|
|
204
|
+
같았다. 기능 회귀는 없다(시험과 작업량). 성능 회귀는 미확정이다 — 60대 두 회차 모두 후보가 느렸고, 2회로는 판단할 수 없다.
|
|
205
|
+
|
|
206
|
+
판정: 효과 미확인으로 보류하고 후보로 보존한다. 할당이 프레임당 약 6 MB(8%) 줄고 단계 계측의 커널이 약 1.1 ms 줄었지만, 전체 프레임 중앙값 차이는
|
|
207
|
+
6 round 중 5회 감소했어도 모두 같은 조건 A/A의 범위(−1.38~+0.93) 안에 있고, p95는 섞였다. 메모리 비용은 없다.
|
|
208
|
+
|
|
209
|
+
다음 차례(사용자 지시의 순서는 여기까지): 남은 후보는 행 `repeat@1` 실행(P2, 9.8 MiB)과 `fromChanges`(7.9 MiB)다. 커널과 할당의
|
|
210
|
+
감소가 측정되는 후보들이 단독으로는 전체 프레임 검출 폭에 닿지 않으므로, 다음 결정이 필요하다 — 후보를 계속 하나씩 볼지, 측정 반복을
|
|
211
|
+
늘려 검출 폭을 낮출지.
|
|
212
|
+
|
|
213
|
+
### P2 첫 후보: 행 `repeat@1` 실행의 오프셋·id 재사용 — 기본 채택 보류 (2026-10-05)
|
|
214
|
+
|
|
215
|
+
figure-model `candidate/repeat-run` `07625f8`(병합하지 않음, a5363bd 위). 일반 평가의 행 실행이 복사본 하나의 오프셋 자세
|
|
216
|
+
`compose(pose, { r: identity(), t })`를 멤버마다가 아니라 복사본마다 한 번 만들고, 행 node의 멤버 key·path가 값으로 같은 동안 복사본 id
|
|
217
|
+
문자열을 다시 쓴다. compose 호출과 그 순서는 같아서 자세는 이전 실행과 bit 단위로 같다. path는 복사본마다 새 배열이다(전체 평가 결과는
|
|
218
|
+
호출자 소유). 자세 단계의 끝 행(terminal) 경로는 이미 오프셋을 재사용하고 있어 손대지 않았다.
|
|
219
|
+
|
|
220
|
+
시험 `src/repeat-run.test.ts` 4개를 구현 전에 썼다: 처음 작성된 행 실행을 시험 안에 두고 fixture 시퀀스 전체에서 −0까지 bit 비교(id·path·
|
|
221
|
+
key·index·shape 포함), 개수 증감, 안쪽 두 행의 개수가 3+2 ↔ 2+3으로 바뀌어 멤버 수는 같고 path만 다른 격자(바깥 행은 z 회전·rise 포함),
|
|
222
|
+
path가 복사본마다·평가마다 다른 배열인지. 결함 다섯(멤버 검사 누락, 멤버 수만 검사, path 공유, 합성 순서 변경, rise 누락)을 심어 모두
|
|
223
|
+
실패를 확인했다. fixture만으로는 멤버 검사 누락과 합성 순서 변경이 통과해서 격자 시험을 더했다. HEAD 구현도 네 시험을 통과한다.
|
|
224
|
+
직접 실행한 시험 763개 통과, 정상 시험 명령은 기존 타입 오류로 차단(`src/render-lod.test.ts:42`, a5781eb). 정상 `npm test`·build가
|
|
225
|
+
통과해야 최종 검증이 끝난다. → 차단 해제: 아래 「타입 수정 뒤 정상 시험·build 검증」(763개 통과, build 성공).
|
|
226
|
+
|
|
227
|
+
측정은 앞 후보들과 같다(보드, 크기만 교대, 6 round × D1·D2·C, 순서 여섯 가지, 워밍업 30, 비계측 150프레임; 60대는 회귀 확인용 2 round).
|
|
228
|
+
things-scene `f287f5f7`, D = figure-model `a5363bd`, C = 이 후보. D 번들은 매개변수 파싱 측정의 기준 번들과 바이트 단위로 같다.
|
|
229
|
+
|
|
230
|
+
| 500대 | A/A (D2−D1) round별 | 후보 (C−D1) round별 |
|
|
231
|
+
| ---------------------- | -------------------------------------- | --------------------------------------- |
|
|
232
|
+
| 프레임 중앙값 차이 ms | −0.32 +0.34 −0.34 +1.38 +0.27 +1.43 | +1.87 −0.13 −0.98 −0.38 −0.70 −1.14 |
|
|
233
|
+
| p95 차이 ms | +0.07 +0.17 −1.85 +3.85 +1.68 +3.59 | +4.57 −0.15 −1.78 −0.46 +1.16 −0.40 |
|
|
234
|
+
|
|
235
|
+
| 500대 | D1 | D2 | C |
|
|
236
|
+
| ----------------------------- | ------------ | ------------ | ------------ |
|
|
237
|
+
| 할당 MB/frame(6회) | 77.19~77.42 | 76.89~77.38 | 76.44~76.82 |
|
|
238
|
+
| 보유 MB(측정 뒤 GC) | 42.7~43.2 | 42.7~43.3 | 42.2~42.7 |
|
|
239
|
+
| 단계 계측 커널 / GC ms | 19.67 / 4.55 | 20.06 / 4.92 | 19.09 / 4.57 |
|
|
240
|
+
|
|
241
|
+
작업량은 같았다: draw call 2001, 삼각형 944012, body 20000, 행 복사 12000(60대: 241, 113292, 2400, 1440).
|
|
242
|
+
|
|
243
|
+
60대(2 round): 프레임 중앙값 차이 −0.22, +0.59 ms(A/A −0.38, −0.11), p95 차이 +4.01, +0.13 ms(A/A +0.28, −0.61). 커널 2.47 → 2.35 ms
|
|
244
|
+
(D2도 2.35), 할당 10.49~10.70 → 10.41~10.42 MB/frame, GC 0.44 → 0.42 ms, 보유 10.1 → 10.3 MB. 검증한 시험과 측정 장면에서 기능 차이를 발견하지 못했다(정상 시험 명령 검증은 아래 「타입 수정 뒤 정상 시험·build 검증」).
|
|
245
|
+
성능 회귀는 미확정이다 — 첫 회차 p95가 A/A 밖으로 나빠졌고, 2회로는 반복되는지 가를 수 없다.
|
|
246
|
+
|
|
247
|
+
원인 확인(별도 할당 프로필, 500대 3 round, 이 실행의 프레임 시간은 판단에 쓰지 않는다): 실제로 서빙된 번들
|
|
248
|
+
(`things-scene/test/browser/figure-vs-js/gap-D/bundle.js` sha256 `ac1f744a…bd7a0`, `gap-C/bundle.js` sha256 `021859f9…c338`)에서
|
|
249
|
+
장면용 `repeat@1` 실행 화살표 함수를 0부터 센 위치로 찾았다 — D 97275행 230217열, C 97275행 230238열. 같은 번들에는 측정 도구가
|
|
250
|
+
`parseFigureAsset`용으로 함께 묶은 node_modules figure-model의 사본이 98711행 12열에 있다. 각 샘플은 자기 node의 selfSize로 한 번만
|
|
251
|
+
세고, 호출 경로 어디에든 그 함수가 있으면 넣었다(부모·자식 inclusive를 더하지 않는다).
|
|
252
|
+
|
|
253
|
+
| 500대, MiB/frame(3회 평균) | D1 | D2 | C |
|
|
254
|
+
| ------------------------------ | ----- | ----- | ----- |
|
|
255
|
+
| 장면용 `repeat@1`(하위 호출 포함) | 9.738 | 9.737 | 9.211 |
|
|
256
|
+
| `parseFigureAsset`용 사본 | 0 | 0 | 0 |
|
|
257
|
+
| 두 사본이 한 경로에 함께 | 0 | 0 | 0 |
|
|
258
|
+
| 전체 | 77.21 | 77.19 | 76.75 |
|
|
259
|
+
|
|
260
|
+
원자료: `allocation-board-500-{D1,D2,C}-r{1,2,3}-17912030…json`(같은 폴더). 서빙 번들 사본은 `figure-vs-js/repeat-run-2026-10-05/`
|
|
261
|
+
(다음 측정이 `gap-*/bundle.js`를 덮으므로). 재집계: things-scene `test/browser/figure-allocation-under.mjs`(91a5ac30) — 위치·번들 해시를
|
|
262
|
+
인자로 받고 해시가 다르면 거절한다.
|
|
263
|
+
행 실행의 할당은 약 0.53 MiB/frame(5.4%) 줄었고, 전체 할당 감소(약 0.45 MiB, 0.6%)와 크기가 맞는다. 할당 감소를 GC 감소로 옮겨 적지 않는다.
|
|
264
|
+
|
|
265
|
+
판정: 기본 채택 보류, 후보와 검증 기록을 보존한다. 500대 프레임 중앙값은 6회 중 5회 줄었지만 +1.87 ms(A/A −0.34~+1.43 밖, 위쪽)와
|
|
266
|
+
−0.98·−0.38·−0.70·−1.14 ms(아래쪽 밖)가 섞여 있고, 범위를 벗어났다는 것만으로 통계적으로 확정된 차이는 아니다. p95는 500대와 60대
|
|
267
|
+
모두 첫 회차에서 나빠졌다. 일관된 개선은 확인하지 못했고, p95 악화의 반복성도 미확정이다. 할당 감소는 이 판정을 대신하지 않는다.
|
|
268
|
+
|
|
269
|
+
### P1 세 번째: `fromChanges`의 중간 객체 — 기본 채택 보류 (2026-10-05)
|
|
270
|
+
|
|
271
|
+
things-scene `candidate/from-changes-dress` `3e7101e6`(병합하지 않음, v10 `91a5ac30` 위). 변경분이 알린 body 하나마다 기존 코드가
|
|
272
|
+
만들던 할당 지점 가운데 셋을 없앴다: (1) `fromChanges`가 먼저 만든 body 객체 — `dressOne`이 이것을 펼쳐 최종 body를 다시 만들었다,
|
|
273
|
+
(2) `dressOne`의 반환 포장 `{ dressed, visible, ref }`, (3) 표시 여부를 읽는 함수 객체. 최종 body는 그림이 담는 결과 객체라 여전히
|
|
274
|
+
만든다(펼치기 대신 처음부터 그 모양으로). 새 자세 객체와 그 배열 넷도 그대로 만든다 — 이전 그림이 그 자세를 계속 갖는다. 모양과 재질
|
|
275
|
+
조회는 `dressOne`과 같은 도움 함수를 쓴다. `evaluation()`·`evaluateOnce()`는 바꾸지 않았다.
|
|
276
|
+
|
|
277
|
+
시험(구현 전): 앞 단계에서 남긴 그림이 뒤 단계 뒤에도 그대로인지, 각 body가 그 모양의 재질 객체 하나를 쓰는지, `evaluation()`에 쓴 값이
|
|
278
|
+
rig가 그린 것에 닿지 않는지(`test/figure-merge.check.js`). 결함 다섯(body마다 새 재질, 표시 여부 무시, 필드 순서, 이전 그림의 자세
|
|
279
|
+
배열에 덮어쓰기, segments 누락)을 심어 모두 실패를 확인했다. `npm run test:figure` 92개(figure-model 소스 대상), `npm test` 2715개 통과.
|
|
280
|
+
|
|
281
|
+
측정: 앞 후보들과 같은 방식. D = things-scene `91a5ac30`, C = 이 후보, figure-model은 둘 다 `a5363bd` dist. 이번 D는 앞 후보들의 D와
|
|
282
|
+
같지 않다(그 사이 v10에 1dd84d39가 들어왔다) — 앞 후보들과 절대 시간을 비교하지 않는다.
|
|
283
|
+
|
|
284
|
+
| 500대 | A/A (D2−D1) round별 | 후보 (C−D1) round별 |
|
|
285
|
+
| ---------------------- | -------------------------------------- | --------------------------------------- |
|
|
286
|
+
| 프레임 중앙값 차이 ms | +0.05 −1.39 +0.18 +6.77 +5.78 +3.16 | +0.69 −0.38 +1.92 +4.78 +4.61 +1.24 |
|
|
287
|
+
| p95 차이 ms | +0.05 −8.80 +1.53 +6.43 +5.20 +3.79 | +1.62 −6.71 +2.13 +4.62 +3.06 −1.29 |
|
|
288
|
+
|
|
289
|
+
| 500대 | D1 | D2 | C |
|
|
290
|
+
| ----------------------------- | ------------ | ------------ | ------------ |
|
|
291
|
+
| 할당 MB/frame(6회) | 76.91~77.33 | 77.01~77.44 | 76.49~76.80 |
|
|
292
|
+
| 보유 MB(측정 뒤 GC) | 42.7~43.3 | 42.7~43.2 | 42.7~43.3 |
|
|
293
|
+
| 단계 계측 `fromChanges` ms | 1.79 | 1.82 | 2.43 |
|
|
294
|
+
| 단계 계측 커널 / GC ms | 19.75 / 5.31 | 20.05 / 4.73 | 19.90 / 4.91 |
|
|
295
|
+
|
|
296
|
+
이번 500대 A/A는 4~6회차에서 +3~+7 ms로 크게 벌어졌다(같은 build끼리) — 실행 간 변동이 컸다는 근거다. 환경 변화가 원인인지는
|
|
297
|
+
확인하지 않았다. 그 폭 안에서는 후보의 프레임 효과를 가를 수 없다. 작업량은 같았다(draw call 2001, 삼각형 944012, 장면 body 20000, 행 복사 12000; 60대 241, 113292, 2400, 1440).
|
|
298
|
+
|
|
299
|
+
60대(2 round): 프레임 중앙값 차이 +0.66, +0.42 ms(A/A +0.29, +0.16), p95 +0.24, −0.37 ms(A/A +0.41, −1.01; 둘 다 A/A 범위 −1.01~+0.41 안이지만 이 범위만으로 효과를 판정하지 않는다). 단계 계측 `fromChanges`
|
|
300
|
+
0.21·0.20 → 0.32 ms, 할당 10.68~10.79 → 10.08~10.39 MB/frame, 보유 같음. 검증한 시험과 측정 장면에서 기능 차이를 발견하지 못했다.
|
|
301
|
+
|
|
302
|
+
원인 확인(별도 할당 프로필, 500대 3 round): 서빙 번들 사본 `figure-vs-js/from-changes-2026-10-05/gap-D.bundle.js` sha256
|
|
303
|
+
`a82cb950…f944`, `gap-C.bundle.js` sha256 `1e851a55…a489`, `fromChanges`는 두 번들 모두 0부터 센 97275행 332754열.
|
|
304
|
+
`test/browser/figure-allocation-under.mjs`로 집계: `fromChanges`(하위 호출 포함) D1 7.938 · D2 7.908 · C 6.932 MiB/frame(약 1.0 MiB,
|
|
305
|
+
12.6% 감소), 전체 77.28 · 77.09 · 76.49.
|
|
306
|
+
|
|
307
|
+
변경 body 수(별도 계측 실행, 비계측 비교와 분리; 같은 Figure, 같은 두 크기, rig 500개, 30프레임, A·B 같음): 프레임마다 changes step 500(rig마다 하나),
|
|
308
|
+
변경분이 알린 body 20000, 새로 만든 최종 body 20000, structure 0. 크기 교대가 모든 body를 옮기므로 이 장면에서는
|
|
309
|
+
변경 body 수가 장면 body 수와 같다 — 다른 입력에서는 다를 수 있다. 할당 감소를 body 수로 나누면 약 52 B/body다(표본 추정).
|
|
310
|
+
|
|
311
|
+
판정: 기본 채택 보류, 후보와 기록을 보존한다. `fromChanges` 할당은 약 1.0 MiB/frame 줄었지만, 단계 계측의 `fromChanges` 시간이 500대에서
|
|
312
|
+
약 0.6 ms, 60대에서 약 0.1 ms 늘었다. 60대 프레임 중앙값은 두 회차 모두 A/A보다 느렸다(성능 회귀 미확정). 500대 프레임
|
|
313
|
+
효과는 이 실행의 A/A 폭 안이라 확인하지 못했다. 시간이 늘어난 이유는 추론이다: 최종 body를 `{ id }`에서 시작해 속성 8~9개를 차례로
|
|
314
|
+
더하면 객체 안의 자리를 넘어 별도 속성 저장소가 생길 수 있고, 펼치기는 처음부터 맞는 크기로 만든다 — 확인하지 않았다.
|
|
315
|
+
|
|
316
|
+
### P1 네 번째: `fromChanges`의 최종 body를 객체 리터럴 하나로 — 기본 채택 보류 (2026-10-05)
|
|
317
|
+
|
|
318
|
+
세 번째 후보(B)에서 `fromChanges` 시간이 늘어난 이유로 「속성을 차례로 붙이면 객체 밖 속성 저장소가 생긴다」를 가설로 적었다. 먼저 작은
|
|
319
|
+
비교로 확인했다(node, 같은 Figure, rig 500개, 두 크기 교대, 워밍업 30·측정 60프레임, `fromChanges`만 시간 측정, 변형마다 별도 프로세스,
|
|
320
|
+
A B C C B A 순서 두 번 = 변형마다 4회, 반복 수는 미리 고정).
|
|
321
|
+
|
|
322
|
+
| 변형 | `fromChanges` ms/frame 중앙값 (범위) | 최종 body의 속성 자리(V8 `%DebugPrint`) |
|
|
323
|
+
| --------------------------------- | ------------------------------------ | ---------------------------------------- |
|
|
324
|
+
| A 기존(91a5ac30, 펼치기) | 2.42 (2.30~2.58) | id만 객체 안, 나머지 8개는 PropertyArray |
|
|
325
|
+
| B 세 번째 후보(차례로 추가) | 2.87 (2.67~3.56) | A와 같음 |
|
|
326
|
+
| C 객체 리터럴 | 2.37 (2.33~2.43) | 9개 모두 객체 안 |
|
|
327
|
+
|
|
328
|
+
가설은 맞지 않았다 — A도 객체 밖 저장소를 쓰므로 그것으로는 B가 A보다 느린 것을 설명하지 못한다(B가 느린 이유는 확인하지 못했다). C는 B보다
|
|
329
|
+
빠르고 A와 비슷했고, A·B가 body마다 만드는 PropertyArray를 만들지 않으므로 별도 후보로 전체 프레임을 측정했다.
|
|
330
|
+
|
|
331
|
+
things-scene `candidate/from-changes-literal` `2b27d899`(B `3e7101e6` 위, 병합하지 않음). 실제로 나오는 네 모양 — 행 복사본(path·key·index)과
|
|
332
|
+
배치 부품(셋 다 없음), 각각 segments 있음·없음 — 을 객체 리터럴 하나로 만들고, 그 밖의 조합은 같은 순서로 하나씩 붙인다(fixture와 측정
|
|
333
|
+
Figure 어디서도 나오지 않는다). 결함 넷(행+segments 필드 순서, 부품+segments의 segments 누락, segments 없는 행의 index 누락, 부품의
|
|
334
|
+
visible 고정)을 심었을 때 셋째가 잡히지 않았다 — segments 없는 행은 `cell-tray`에만 있고 변경분 시험에 없었다. `cell-tray`를 변경분과
|
|
335
|
+
스냅샷 비교 시험에 더해 넷 모두 실패를 확인했다(더한 시험은 A·B·C 모두 통과). `npm run test:figure` 92개, `npm test` 2715개 통과.
|
|
336
|
+
|
|
337
|
+
측정: D = things-scene `91a5ac30`(세 번째 후보와 같은 번들, 서빙 번들 sha256 `a82cb950…f944`도 같다), C = 이 후보, figure-model 둘 다
|
|
338
|
+
`a5363bd` dist. 앞 후보들과 절대 시간을 비교하지 않는다.
|
|
339
|
+
|
|
340
|
+
| 500대 | A/A (D2−D1) round별 | 후보 (C−D1) round별 |
|
|
341
|
+
| ---------------------- | -------------------------------------- | --------------------------------------- |
|
|
342
|
+
| 프레임 중앙값 차이 ms | +0.44 −9.32 +5.20 −1.66 −0.22 +0.07 | +0.61 −9.57 +1.54 −1.71 +0.63 +0.67 |
|
|
343
|
+
| p95 차이 ms | +0.56 −9.82 +4.04 −2.81 −0.47 −0.71 | −0.09 −11.97 +2.00 −6.42 +2.27 −0.54 |
|
|
344
|
+
|
|
345
|
+
| 500대 | D1 | D2 | C |
|
|
346
|
+
| ----------------------------- | ------------ | ------------ | ------------ |
|
|
347
|
+
| 할당 MB/frame(6회) | 76.96~77.28 | 76.90~77.29 | 74.80~75.46 |
|
|
348
|
+
| 보유 MB(측정 뒤 GC) | 42.7~43.3 | 42.7~43.3 | 43.0 |
|
|
349
|
+
| 단계 계측 `fromChanges` ms | 2.17 | 1.99 | 1.88 |
|
|
350
|
+
| 단계 계측 커널 / GC ms | 21.20 / 4.91 | 19.91 / 4.67 | 19.91 / 4.35 |
|
|
351
|
+
|
|
352
|
+
A/A가 2회차 −9.3 ms, 3회차 +5.2 ms로 크게 벌어졌다 — 실행 간 변동이 컸다는 근거이고 원인은 확인하지 않았다. 그 폭 안에서 후보의 프레임
|
|
353
|
+
효과는 가를 수 없다. 작업량은 같았다(draw call 2001, 삼각형 944012, 장면 body 20000, 행 복사 12000; 60대 241, 113292, 2400, 1440).
|
|
354
|
+
|
|
355
|
+
60대(2 round): 프레임 중앙값 차이 0.00, +0.11 ms(A/A +0.02, +0.14), p95 −0.30, +0.13 ms(A/A −0.34, +0.55), 단계 계측 `fromChanges`
|
|
356
|
+
0.20·0.22 → 0.28 ms, 할당 10.53 → 10.23~10.30 MB/frame, GC 0.44·0.47 → 0.40 ms, 보유 같음(10.1).
|
|
357
|
+
|
|
358
|
+
원인 확인(별도 할당 프로필, 500대 3 round): 서빙 번들 사본 `figure-vs-js/from-changes-literal-2026-10-05/`(D sha256 `a82cb950…f944`,
|
|
359
|
+
C `97744f65…861c`), `fromChanges`는 둘 다 0부터 센 97275행 332754열, `test/browser/figure-allocation-under.mjs`로 집계. `fromChanges`
|
|
360
|
+
(하위 호출 포함) D1 7.909 · D2 7.892 · C 5.955 MiB/frame(약 1.95 MiB, 24.6% 감소), 전체 77.05 · 77.12 · 75.07(약 2.0 MiB, 2.6%).
|
|
361
|
+
변경 body 수는 이 후보 C(`2b27d899`)에서 직접 다시 셌다(세 번째 후보와 같은 별도 계측 스크립트, 비계측 비교와 분리; rig 500개, 30프레임):
|
|
362
|
+
프레임마다 changes step 500, 변경분이 알린 body 20000, 새로 만든 최종 body 20000, structure 0 — 세 번째 후보의 A·B 값과 같다.
|
|
363
|
+
|
|
364
|
+
관찰 정리: 할당은 `fromChanges`에서 약 2 MiB/frame 줄었다. 단계 계측의 `fromChanges` 시간은 500대에서 A의 두 실행
|
|
365
|
+
(2.17·1.99)보다 낮았고, 60대에서는 0.20·0.22 → 0.28 ms로 높았다. 500대 GC 시간은 두 A보다 낮았다(4.91·4.67 → 4.35 ms) — 할당 감소에서
|
|
366
|
+
GC 감소를 끌어내지 않고 측정값으로만 적는다. 전체 프레임 개선은 이 실행의 A/A 폭 때문에 확인하지 못했다. 60대 프레임은 A/A와 구분되지 않았다.
|
|
367
|
+
|
|
368
|
+
판정: 기본 채택 보류, 후보와 기록을 보존한다. 할당 2.6% 감소는 확인했지만, 전체 프레임 개선은 구분되지 않았고 60대의 `fromChanges`
|
|
369
|
+
시간은 늘었다. 다음은 미세 최적화보다 측정의 실행 간 변동 원인을 먼저 확인한다(A/A −9.32~+5.20 ms).
|
|
370
|
+
|
|
371
|
+
### 타입 수정 뒤 정상 시험·build 검증 (2026-10-05)
|
|
372
|
+
|
|
373
|
+
`src/render-lod.test.ts:42`의 타입 오류는 다른 세션이 main `d6bdd66`(사용자 지시)으로 고쳤다. 고친 diff는 시험 쪽 한 줄이다:
|
|
374
|
+
|
|
375
|
+
```diff
|
|
376
|
+
- assert.equal(figureRenderSegmentsOf(unsupported, 'coarse'), 32)
|
|
377
|
+
+ assert.equal(figureRenderSegmentsOf(unsupported as never, 'coarse'), 32)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
후보 브랜치는 그대로 두고, 따로 만든 분리 worktree에서 후보의 기준 커밋에 `d6bdd66`만 얹어(`git cherry-pick -n`, 커밋하지 않음) 정상
|
|
381
|
+
`npm test`(타입 검사 + 시험)와 `npm run build`를 돌렸다. main 전체로 rebase하지 않았다 — 측정한 대상과 같게 두기 위해서다.
|
|
382
|
+
|
|
383
|
+
| 대상 | 기준 커밋 | 얹은 것 | `npm test` | build |
|
|
384
|
+
| ---------------------------------------- | --------- | ------------- | ---------------------- | ----- |
|
|
385
|
+
| figure-model main | `5d8db89` | (d6bdd66 포함) | 759 통과, 0 실패 | 성공 |
|
|
386
|
+
| `candidate/repeat-run` (P2 첫 후보) | `07625f8` | `d6bdd66` | 763 통과, 0 실패 | 성공 |
|
|
387
|
+
| `candidate/extrude-param-cache` (P1 둘째) | `fc9a7b3` | `d6bdd66` | 762 통과, 0 실패 | 성공 |
|
|
388
|
+
|
|
389
|
+
두 후보의 「정상 시험 명령은 타입 오류로 차단」 상태는 이 검증으로 해제한다. 성능 판정(둘 다 기본 채택 보류)은 바뀌지 않는다.
|
|
390
|
+
things-scene 후보(`candidate/from-changes-dress`, `candidate/from-changes-literal`)는 이 오류와 무관하게 `npm test`(2715)와
|
|
391
|
+
`npm run test:figure`(92)를 통과했다. 발행하지 않았다.
|
|
392
|
+
|
|
393
|
+
### 측정의 실행 간 변동 진단 (2026-10-05, 후보 판정과 별개)
|
|
394
|
+
|
|
395
|
+
후보 코드 변경 없이 진행했다. 보류한 후보들의 성능 판정을 대신하지 않는다.
|
|
396
|
+
|
|
397
|
+
1. 기존 결과 다시 보기(새 측정 없음). 오늘 500대 6 round 다섯 번의 `gap-*.json`을 실행 순서(ORDERS)대로 늘어놓았다. 실행 18회의 프레임
|
|
398
|
+
중앙값 범위는 형상 키 49.3~51.6, 매개변수 파싱 48.8~52.0, `repeat@1` 48.8~53.9(13~17번째 실행 구간), `fromChanges` B 49.5~56.8
|
|
399
|
+
(11~16번째), 리터럴 C 49.8~60.3 ms(6~10번째)였다. 서서히 변하는 흐름은 없었고, 연속된 몇 번의 실행이 함께 느려졌으며 그때 돈 팔이
|
|
400
|
+
무엇이든 느렸다. round별 A/A 차이가 크게 나온 것은 이 구간이 어느 round에 걸렸는지와 맞물린다. 그 시각의 부하·열 상태는 기록되지 않았다.
|
|
401
|
+
|
|
402
|
+
2. 고정 측정. things-scene `82b84ead`의 `TRACE=1`(실행 앞뒤에서만 기록하며 측정 프레임 안에는 아무것도 더하지 않는다). 세 팔 모두 같은 build
|
|
403
|
+
(things-scene `91a5ac30`, figure-model `a5363bd`), 보드 500대, 6 round, 워밍업 30, 측정 150, 프로필 없음. 원자료
|
|
404
|
+
`figure-vs-js/gap-1791206726787.json`. 워밍업과 측정 구간은 같은 `frame()`(크기 변경 + 그리기 + 1픽셀 읽기를 `performance.now()`로)과
|
|
405
|
+
프레임 사이 `requestAnimationFrame` 한 번으로 잰다 — 타이밍 방식이 같으므로 두 구간의 추이를 비교할 수 있다. 다만 워밍업 초반에는
|
|
406
|
+
초기 컴파일·캐시 영향이 들어 있다.
|
|
407
|
+
|
|
408
|
+
| 항목 | 결과 |
|
|
409
|
+
| ---------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
410
|
+
| A/A (D2−D1) round별 중앙값 / p95 ms | +1.31 +1.25 −0.46 +0.08 −0.20 +0.50 / +2.84 −0.17 −2.95 +0.22 +0.14 +2.44 |
|
|
411
|
+
| 같은 build (C−D1) round별 중앙값 / p95 ms | +0.02 +0.06 −0.10 −0.10 −1.06 +0.06 / +1.00 −0.71 −2.60 +0.74 −0.04 −0.41 |
|
|
412
|
+
| 실행 18회 중앙값 | 1~3번째(첫 round) 53.5·54.8·53.5, 4~18번째 49.4~51.7 ms |
|
|
413
|
+
| 1분 부하 평균(실행 직전) | 1~3번째 5.8·5.2·5.7, 4~18번째 2.9~4.5 |
|
|
414
|
+
| 열·성능 경고(`pmset -g therm`) | 18회 모두 기록 없음 |
|
|
415
|
+
|
|
416
|
+
이번 실행에서는 앞의 세 측정 같은 연속 느린 구간이 나오지 않았고, A/A 중앙값 차이는 −0.46~+1.31 ms였다(앞의 측정 −9.32~+6.77).
|
|
417
|
+
|
|
418
|
+
- 부하와의 상관관계 후보: 처음 세 실행이 느렸고 그때 1분 부하 평균이 가장 높았다. 그 시각 CPU 상위에 다른 세션의 `npm run build`,
|
|
419
|
+
CPU 169%인 `node`, Spotlight 색인(`mds_stores`), `syspolicyd`가 있었다. 다만 첫 round라는 순서 효과와 겹쳐 있어 둘을 가를 수 없다.
|
|
420
|
+
실행 중간에도 다른 앱(ChatGPT Codex 140%, `node` 231% 등)이 CPU 상위에 있었지만 그 실행들은 느리지 않았다. 상관관계로만 적는다.
|
|
421
|
+
- 정리 확인: 18회 모두 정리 뒤 WebGL context가 해제되었고(`isContextLost` true), renderer 기록의 geometry는 5 → 0이었다(texture 4 → 2,
|
|
422
|
+
program 3 → 3은 해제 뒤에도 renderer 기록에 남은 수). 열린 target 수는 매번 3 → 3으로 돌아왔고, 페이지 시작 시 JS heap은 18회 모두
|
|
423
|
+
7.5 MB로 같았다. 이것은 페이지 단위 정리 확인이며 브라우저 전체(GPU 프로세스 등)에 자원이 남지 않았다는 뜻은 아니다. GPU 프로세스의
|
|
424
|
+
누적 CPU 시간은 실행마다 약 1.7 s씩 늘었는데, 누적값이라 잔류 여부를 보여 주지 않는다.
|
|
425
|
+
- 워밍업 30프레임 뒤의 안정화: 측정 150프레임의 처음 10프레임 중앙값이 마지막 10프레임 중앙값보다 큰 실행이 18회 중 15회였다.
|
|
426
|
+
차이는 +0.1~+7.6 ms(중앙값 약 +2.5 ms), 반대인 실행은 셋(−0.9, −2.1, −8.0 ms)이었다. 보조 지표인 기울기(150프레임 동안 변화량)는
|
|
427
|
+
중앙값 약 −1.4 ms였고 +5.8 ms인 실행 하나가 있었다(GC 같은 순간적인 튐에 민감하다). 워밍업 30프레임 뒤에도 대부분의 실행에서 시간이
|
|
428
|
+
계속 낮아지고 있었다 — 측정 구간이 아직 안정 상태가 아니다.
|
|
429
|
+
|
|
430
|
+
결론: 앞의 측정들에서 A/A가 크게 벌어진 원인은 미확정이다. 확인한 것은 (1) 연속된 느린 실행 구간이 있었다는 것, (2) 부하 평균이 높았던
|
|
431
|
+
시각과 느린 실행이 겹친 사례가 한 번 있었다는 것(상관관계), (3) 워밍업 30프레임 뒤에도 측정 구간 안에서 시간이 대개 2~3 ms 더 낮아진다는
|
|
432
|
+
것이다. (3)은 판정 전 측정 조건(워밍업 길이)을 다시 정할 근거가 될 수 있다 — 바꾸기 전에 사용자와 정한다.
|
|
433
|
+
|
|
434
|
+
### 측정 조건 검증: 워밍업 300 · 측정 300 (2026-10-05, 측정 전에 고정)
|
|
435
|
+
|
|
436
|
+
후보 비교 없이 같은 build로 조건만 검증한다. 세 팔 모두 things-scene `91a5ac30` · figure-model `a5363bd`, 보드 500대, 기존 ORDERS 6 round(실행
|
|
437
|
+
18회), 워밍업 300, 측정 300, `TRACE=1`, 프로필 없음. 안정화는 실행마다 측정 프레임 1~100, 101~200, 201~300의 중앙값으로 본다.
|
|
438
|
+
안정으로 보는 기준(측정 전에 정함): 실행 18회의 (1~100 중앙값 − 201~300 중앙값)의 중앙값이 ±0.5 ms 안이고, 양수인 실행이 18회 중 12회 이하.
|
|
439
|
+
결과를 보고 워밍업을 더 늘리지 않는다. 부하 평균으로 실행을 고르거나 빼지 않는다. 측정 동안 다른 세션(허드렛일 담당, Operato-board
|
|
440
|
+
figure-model 운영, Operato-twin 페이지 UI/UX 개선)에 새 build·test·분석 작업을 미뤄 달라고 요청했다. Spotlight 등 나머지 부하는 기록한다.
|
|
441
|
+
안정되면 리터럴 후보부터 같은 조건으로 다시 비교하고, 안정되지 않으면 작은 후보의 판정을 계속 보류하고 측정 원인을 더 확인한다.
|
|
442
|
+
|
|
443
|
+
결과(원자료 `figure-vs-js/gap-1791207788161.json`, 실행 18회 13:28:41Z~13:42:16Z):
|
|
444
|
+
|
|
445
|
+
- 실행 안 안정화 — 미리 정한 기준 충족. (1~100 중앙값 − 201~300 중앙값)의 중앙값 +0.13 ms(기준 ±0.5 안), 양수 10회(기준 12회 이하),
|
|
446
|
+
범위 −3.8~+7.0 ms. 워밍업 300프레임 뒤의 측정 구간에서는 앞선 30프레임 조건에서 본 「계속 낮아지는」 추세가 보이지 않았다.
|
|
447
|
+
- 실행 사이 변동 — 남았다. 실행 중앙값은 1번째 57.6, 2~13번째 48.9~50.5, 14·17·18번째 53.7·54.6·53.1, 15번째 50.7 ms. A/A(D2−D1)
|
|
448
|
+
round별 중앙값 −7.08 −0.89 +0.63 −1.02 +2.95 −5.30, p95 −10.28 −3.41 +0.63 −6.30 +2.83 −5.51. 같은 build(C−D1) 중앙값 −8.23 −1.08
|
|
449
|
+
+0.71 −0.67 −1.60 −1.54. 느린 실행이 낀 round(1·5·6)에서 차이가 크다. 느린 실행 셋(14·17·18)은 그 안에서 시간이 오르거나(14·17:
|
|
450
|
+
가운데·마지막 구간이 높다) 처음이 높았다(18) — 실행 도중의 변화다.
|
|
451
|
+
- 다른 작업과 시각(상관관계 후보, 원인 확정 아님): 1번째 실행(13:28:41Z) 직전 1분 부하 평균 25.6, CPU 상위가 `node` 셋. 같은 무렵
|
|
452
|
+
Operato-twin UX 세션의 파일 저장으로 :4000 webpack이 한 번 다시 묶었을 수 있다(13:28:39Z 무렵, 그 세션의 알림). 허드렛일 담당 세션의
|
|
453
|
+
tf 발행은 13:36:04Z 응답 시각에 「방금 끝났다」였고 시작 시각을 몰라 어느 실행과 겹쳤는지는 미확정이다. Operato-twin UX 세션은 사용자
|
|
454
|
+
지시로 22:38(13:38Z, 분 단위; 그 알림의 도착 13:38:09Z)부터 plant `npm test`, twin client tsc, :3000 twin 개발 서버 기동을 다시
|
|
455
|
+
시작했다 — 12번째 실행(13:37:28Z~13:38:15Z)은 실행 도중에 이 작업이 시작되어 겹쳤고, 13~18번째(13:38:15Z 이후 시작)는 시작 전부터
|
|
456
|
+
겹쳤다. 느린 실행 14·17·18은 모두 13:38Z 이후다(15·16번째는 느리지 않았다). 부하 평균은 1번째를 빼면 3.9~7.8로 느린 실행과 일관되게
|
|
457
|
+
대응하지 않았다. 열·성능 경고는 18회 모두 없었다. 실행은 하나도 빼지 않았다.
|
|
458
|
+
- 정리 확인(페이지 단위): 18회 모두 context 해제, 열린 target 3 → 3, 페이지 시작 JS heap 7.5 MB.
|
|
459
|
+
|
|
460
|
+
판단: 워밍업 300 · 측정 300으로 실행 안의 안정화는 18회를 모아 본 기준을 충족했다 — 모든 실행이 안정적이었다는 뜻은 아니다(14·15·17·18번째는
|
|
461
|
+
실행 안에서 구간 중앙값이 2~7 ms 움직였다). 실행 사이의 변동은 다른 작업이 돌던 시각의 실행에서 크게 남아,
|
|
462
|
+
같은 build의 A/A가 round에 따라 ±5~8 ms 벌어졌다. 원인은 미확정이다(다른 작업과 시각이 겹친 것은 상관관계).
|
|
463
|
+
|
|
464
|
+
다음(사용자 결정): 다른 세션의 사용자 지시 작업은 멈추게 하지 않고, 그 작업이 끝난 조용한 시간대에 리터럴 후보를 같은 조건(워밍업 300 ·
|
|
465
|
+
측정 300 · 18회, D1·D2 A/A 대조)으로 다시 비교한다. 실행 제외나 결과에 따른 회차 추가 없이 판단하고, 다시 변동에 묻히면 후보는 보류로
|
|
466
|
+
마무리한다.
|
|
467
|
+
|
|
468
|
+
### 리터럴 후보 재비교: 워밍업 300 · 측정 300 · 18회 — 보류로 마무리 (2026-10-05)
|
|
469
|
+
|
|
470
|
+
D = things-scene `91a5ac30`, C = `candidate/from-changes-literal` `2b27d899`, figure-model 둘 다 `a5363bd` dist, 보드 500대, 기존 ORDERS 6 round,
|
|
471
|
+
워밍업 300, 측정 300, `TRACE=1`, 프로필 없음. 실행 18회 13:47:49Z~14:02:20Z, 제외 없음. 원자료 `figure-vs-js/gap-1791208940162.json`.
|
|
472
|
+
|
|
473
|
+
조건 기록: build·test는 억제했지만 파일 저장에 따른 개발 서버 재빌드는 남은 상태다. 허드렛일 담당은 13:46:42Z부터, Operato-twin UX는
|
|
474
|
+
13:47:27Z부터 30분 동안 새 build·test·tsc·서버 기동을 하지 않기로 했고, Operato-twin UX는 사용자 지시로 화면 코드 수정을 계속했다(저장마다
|
|
475
|
+
떠 있는 :3000의 tsc --watch·webpack과 :4000 webpack이 몇 초씩 다시 묶는다). Operato-board figure-model 운영은 측정 시작 뒤 13:56:42Z에 「작업
|
|
476
|
+
끝, 30분 동안 새 build·test 없음, :3600 서버는 떠 있되 쓰지 않음」이라 답했다 — 1~10번째 실행 동안의 그 세션 작업은 확인하지 못했다. 시작
|
|
477
|
+
직전 CPU 상위에 build·test 프로세스는 없었고 1분 부하 평균 3.27이었지만, 이것이 측정 도중의 무부하를 보장하지 않는다. 7번째 실행
|
|
478
|
+
(13:52:40Z) 직전 CPU 상위에 `tsc … tsconfig.json --noEmit`이 있었다(어느 세션 것인지 확인하지 못했다).
|
|
479
|
+
|
|
480
|
+
측정 시간 안에 저장된 소스 파일(수정 시각, Z): 13:48:01 twin 페이지(1번째 실행 중), 13:49:44 board `board-figures.ts`·13:49:53 board 번역
|
|
481
|
+
다섯(3번째 실행 중), 13:55:24 twin 페이지(10번째), 13:58:27 twin 페이지(14번째), 14:01:16 twin 페이지와 번역 다섯(17번째 끝 무렵)·14:01:38 twin
|
|
482
|
+
번역 캐시(18번째 실행 중).
|
|
483
|
+
|
|
484
|
+
| 실행 | 시작 Z | 팔 | 중앙값 | p95 | 구간 중앙값(1~100 / 101~200 / 201~300) | 그 실행 중 저장 |
|
|
485
|
+
| ---- | --------- | -- | ------ | ---- | -------------------------------------- | --------------------- |
|
|
486
|
+
| 1 | 13:47:49 | D1 | 50.4 | 59.7 | 49.9 / 49.7 / 51.8 | 13:48:01 |
|
|
487
|
+
| 2 | 13:48:38 | D2 | 52.8 | 61.0 | 50.6 / 55.4 / 53.1 | 없음 |
|
|
488
|
+
| 3 | 13:49:28 | C | 50.2 | 57.0 | 50.0 / 50.8 / 50.0 | 13:49:44 · 13:49:53 |
|
|
489
|
+
| 4~11 | 13:50:17~ | — | 49.2~50.6 | 53.3~58.2 | 차이 1 ms 안팎 | 13:55:24(10번째) |
|
|
490
|
+
| 12 | 13:56:36 | D2 | 56.5 | 67.7 | 56.7 / 57.9 / 54.1 | 없음 |
|
|
491
|
+
| 13·14 | 13:57:28~ | C·D2 | 50.1·49.4 | 55.9·55.2 | — | 13:58:27(14번째) |
|
|
492
|
+
| 15 | 13:59:03 | D1 | 54.6 | 61.8 | 55.3 / 55.9 / 51.9 | 없음 |
|
|
493
|
+
| 16·17 | 13:59:53~ | D2·D1 | 50.1·50.5 | 58.3·57.1 | — | 14:01:16(17번째 끝 무렵) |
|
|
494
|
+
| 18 | 14:01:30 | C | 52.5 | 61.6 | 50.7 / 51.7 / 56.0 | 14:01:38 |
|
|
495
|
+
|
|
496
|
+
느린 실행 넷(2·12·15·18) 가운데 저장과 겹친 것은 18번째 하나이고, 저장이 있었던 1·3·10·14번째는 느리지 않았다 — 저장 재빌드와 느린 실행은
|
|
497
|
+
일관되게 대응하지 않는다(상관관계로도 약하다).
|
|
498
|
+
|
|
499
|
+
정정(측정 뒤 Operato-twin UX 세션의 알림, 시각은 그 세션이 적은 KST를 Z로 옮긴 것이며 대략값): 그 세션은 측정 동안 저장 말고도 직접
|
|
500
|
+
실행한 작업이 있었다 — 13:49:01 저장으로 한 파일이 117만 줄이 되어 `tsc --watch`가 그것을 처리했고(13:51 `git checkout`으로 되돌림),
|
|
501
|
+
`tsc --noEmit`(13:49~13:50, 13:52:4x, 13:59~14:01 두 번), 전체 `tsc` emit(13:56:03~06), twin `npm test`(13:56:44, 13:59:11, 14:01:55, 각 약 1분).
|
|
502
|
+
같은 목록의 operato-board·webcomponents 파일(13:47~13:49)은 그 세션 것이 아니다(누구 것인지 확인하지 못했다). 실행 구간과 맞추면:
|
|
503
|
+
|
|
504
|
+
| 느린 실행 | 그 실행과 겹친 작업 |
|
|
505
|
+
| --------- | ------------------- |
|
|
506
|
+
| 2 (52.8) | 117만 줄 파일 저장과 `tsc --watch` 처리, `tsc --noEmit` |
|
|
507
|
+
| 12 (56.5) | twin `npm test` 시작(13:56:44) |
|
|
508
|
+
| 15 (54.6) | twin `npm test` 시작(13:59:11), `tsc --noEmit` |
|
|
509
|
+
| 18 (52.5) | twin `npm test` 시작(14:01:55) |
|
|
510
|
+
|
|
511
|
+
느린 실행 넷이 모두 무거운 작업(큰 파일의 tsc 처리 또는 `npm test` 시작)과 겹쳤다. `tsc --noEmit`이나 저장만 겹친 실행(3·4·7·14·17)과
|
|
512
|
+
`npm test` 끝자락만 겹친 실행(13·16)은 느리지 않았다. 앞 문단의 「일관되게 대응하지 않는다」는 저장 재빌드에 대해서만 맞고, 직접 실행된
|
|
513
|
+
작업까지 보면 느린 실행과 일관되게 겹친다. 시각이 대략값이고 한 측정의 관찰이므로 상관관계로 적으며, 원인으로 확정하지 않는다. 이 측정은
|
|
514
|
+
「build·test 억제」 조건을 다 지키지 못한 상태였다. 열·성능 경고는 18회 모두 없었다. 정리 확인(페이지 단위)은 18회 모두 context 해제, target 3 → 3,
|
|
515
|
+
시작 JS heap 7.5 MB. 실행 안 안정화: (1~100 − 201~300) 중앙값 +0.38 ms, 양수 12회 — 미리 정한 기준(±0.5, 12회 이하)을 겨우 충족했다(18회를
|
|
516
|
+
모아 본 판단이며 2·12·15·18번째는 실행 안에서도 움직였다).
|
|
517
|
+
|
|
518
|
+
| 500대 | A/A (D2−D1) round별 | 후보 (C−D1) round별 |
|
|
519
|
+
| ---------------------- | -------------------------------------- | --------------------------------------- |
|
|
520
|
+
| 프레임 중앙값 차이 ms | +2.35 −0.24 −0.18 +6.77 −5.27 −0.42 | −0.26 +1.19 −0.15 +0.85 −4.50 +2.00 |
|
|
521
|
+
| p95 차이 ms | +1.29 −2.40 +0.79 +11.41 −6.62 +1.23 | −2.68 +2.51 +0.39 +1.43 −5.89 +4.49 |
|
|
522
|
+
|
|
523
|
+
| 500대 | D1 | D2 | C |
|
|
524
|
+
| ----------------------------- | ------------ | ------------ | ------------ |
|
|
525
|
+
| 프레임 중앙값 / p95 (6회 중앙값) | 50.08 / 56.68 | 49.81 / 57.32 | 50.36 / 57.33 |
|
|
526
|
+
| 할당 MB/frame(6회) | 77.17~77.52 | 76.97~77.38 | 74.91~75.28 |
|
|
527
|
+
| 보유 MB(측정 뒤 GC) | 42.8~43.4 | 42.9~43.4 | 42.6~43.2 |
|
|
528
|
+
| 단계 계측 `fromChanges` ms | 1.86 | 1.99 | 2.09 |
|
|
529
|
+
| 단계 계측 커널 / GC ms | 20.06 / 4.71 | 20.43 / 4.90 | 20.37 / 4.41 |
|
|
530
|
+
|
|
531
|
+
작업량은 같았다(draw call 2001, 삼각형 944012, 장면 body 20000, 행 복사 12000).
|
|
532
|
+
|
|
533
|
+
판정: 보류로 마무리한다. 후보와 기록을 보존한다. 할당은 약 2 MB/frame 줄었지만, 프레임 중앙값·p95 차이는 같은 실행의 A/A 변동
|
|
534
|
+
(중앙값 −5.27~+6.77, p95 −6.62~+11.41 ms) 안에 묻혔고, 6회 중앙값으로도 C가 두 A보다 낫지 않았다. 단계 계측의 `fromChanges`는 두 A보다
|
|
535
|
+
높았다. 실행 사이 변동의 원인은 미확정이지만, 이 측정에서는 느린 실행 넷이 모두 다른 세션의 무거운 작업과 겹쳤다(위 정정, 상관관계).
|
|
536
|
+
|
|
537
|
+
### 조용한 시간대의 A/A 진단 (2026-10-05, 측정 전에 고정)
|
|
538
|
+
|
|
539
|
+
같은 build(things-scene `91a5ac30` · figure-model `a5363bd`)를 세 팔 모두에, 보드 500대, 기존 ORDERS 6 round(18회), 워밍업 300, 측정 300,
|
|
540
|
+
`TRACE=1`, 프로필 없음. 시작 조건: 세 세션(허드렛일 담당, Operato-board figure-model 운영, Operato-twin 페이지 UI/UX 개선)이 모두 「build·test·tsc·
|
|
541
|
+
서버 기동·파일 저장이 없는 25분」을 알려 온 뒤. 측정 중 CPU 기록: 5초마다 UTC 시각, 부하 평균(`sysctl vm.loadavg`), CPU 상위 8개 프로세스
|
|
542
|
+
(`ps -Ao pcpu,pid,etime,command -r`)를 파일에 적는다. 수집 한 번의 비용은 CPU 약 19 ms(10회에 user 0.06 s + sys 0.13 s)로, 5초 간격이면 한 코어의
|
|
543
|
+
약 0.4%다. 기록은 측정 프레임과 맞춰 보지 않고 실행 구간 단위로 맞춘다.
|
|
544
|
+
판단: 실행을 제외하거나 회차를 늘리지 않는다. 측정 중 다른 작업이 끼면 「협의한 조건을 충족하지 못한 측정」으로 기록하고, 추가 재측정 대신
|
|
545
|
+
별도 머신이나 전용 측정 시간 확보를 다음 선택으로 둔다. 조용한 조건에서 변동이 줄면 외부 부하 설명을 뒷받침하지만, 원인을 단독으로
|
|
546
|
+
확정하지는 않는다. 후보 채택과 발행은 계속 보류한다.
|
|
547
|
+
|
|
548
|
+
결과(원자료 `figure-vs-js/gap-1791210931321.json`, CPU 기록 168개, 실행 18회 14:21:28Z~14:35:31Z, 제외 없음):
|
|
549
|
+
|
|
550
|
+
조건 — 협의한 조건을 다 충족하지 못한 측정이다.
|
|
551
|
+
- 시작 판단의 실수: 허드렛일 담당의 확인이 14:21:27Z에 왔는데 14:13Z쯤으로 잘못 보고 바로 시작했다. 예상 끝(14:37Z)이 Operato-board의 구간
|
|
552
|
+
(14:07:35Z부터 25분)을 넘었고, 측정 중 14:22:18Z에 같은 세션(세션 id 같음)이 14:40Z까지 늘려 주었다. 실제 끝은 14:35:31Z로 세 세션이 알려 온
|
|
553
|
+
구간 안이다. Operato-twin UX는 「비어 있음, 25분 약속은 못 함」이었다.
|
|
554
|
+
- 측정 직전에 개발 서버 셋이 기동했다: plant `:4000`(14:20:42Z), office(14:20:49Z), twin `--webpack`(14:21:04Z) — 각각 `npm run build:server &&
|
|
555
|
+
copy:files && concurrently tsc --watch + things-factory-dev`. 시작 25~45초 전이라 첫 컴파일·묶음이 1~2번째 실행과 겹쳤을 수 있다. 어느 세션이
|
|
556
|
+
띄웠는지는 확인하지 못했다. CPU 기록에 잡힌 것은 그 `tsc --watch`(14:22:33Z 8.8%)와 twin `things-factory-dev`(14:23:08Z 7.5%)뿐이다.
|
|
557
|
+
- 그 밖의 부하: Spotlight(`mds`·`mds_stores`, 시작 무렵 최대 약 150%, 15~17번째 실행 중 평균 28~50%), 사용자 Chrome(1~3·18번째 평균 34~90%),
|
|
558
|
+
`mediaanalysisd`(5번째 42%). 1분 부하 평균은 1번째 11.5, 2번째 6.3, 이후 2.3~3.8. 새로 시작된 build·test(`npm test`, `tsc --noEmit` 등)는 CPU
|
|
559
|
+
기록에 없었다(5초 간격이므로 짧은 작업은 빠질 수 있다).
|
|
560
|
+
|
|
561
|
+
| 항목 | 결과 |
|
|
562
|
+
| ---------------------------------------- | --------------------------------------------------------------------- |
|
|
563
|
+
| A/A (D2−D1) round별 중앙값 / p95 ms | −0.25 −0.22 −0.49 −0.47 +0.22 +0.13 / +0.14 +1.13 −0.09 −0.37 +0.96 +0.48 |
|
|
564
|
+
| 같은 build (C−D1) round별 중앙값 / p95 ms | −0.80 −0.04 −0.75 −0.50 −0.12 −0.22 / +0.57 +1.09 −0.92 −1.02 +0.16 −0.36 |
|
|
565
|
+
| 실행 18회 중앙값 | 49.0~50.3 ms (1·2번째 50.3·50.0) |
|
|
566
|
+
| 실행 안 안정화 | (1~100 − 201~300) 중앙값 +0.30 ms, 양수 14회 — 미리 정한 기준(12회 이하) 밖 |
|
|
567
|
+
|
|
568
|
+
해석: 같은 build의 A/A 중앙값 차이가 −0.49~+0.22 ms(앞의 측정들 ±5~9 ms)로 줄었고, 느린 실행 구간이 없었다. 다른 세션의 무거운 작업(`npm test`,
|
|
569
|
+
큰 파일의 tsc 처리)이 없던 이번 측정에서 변동이 줄었으므로 외부 부하 설명을 뒷받침한다. 원인을 단독으로 확정하지는 않는다 — 개발 서버 기동,
|
|
570
|
+
Spotlight, 사용자 Chrome 부하가 있었는데도 변동이 작았다는 점에서, 어떤 부하가 영향을 주는지는 가려지지 않았다. 측정 구간 안의 추세는 양수
|
|
571
|
+
실행이 14회로 기준(12회 이하)을 넘었다(차이의 중앙값 +0.30 ms는 기준 안).
|
|
572
|
+
이번 A/A 변동은 작았지만, 1 ms 효과의 검출 가능성은 아직 검증하지 않았다. 실행 안 안정화 기준 둘 가운데 하나를 충족하지 못했으므로
|
|
573
|
+
완전히 안정됐다고 판단하지 않는다.
|
|
574
|
+
|
|
575
|
+
다음 선택(사용자 결정, 10-06): 전용 측정 시간 확보. 이 머신에서도 변동이 줄어든 근거가 있으므로 별도 머신으로 옮기기 전에 이 조건을 먼저 제대로
|
|
576
|
+
확보한다. 그 시간에는 build·test·tsc·파일 저장·개발 서버 기동을 멈추고 측정용 브라우저만 쓴다. 확인 시각과 종료 예정 시각을 명시해 구간을
|
|
577
|
+
확보한 뒤, 기존 조건(워밍업 300 · 측정 300 · 18회)으로 A/A를 먼저 검증한다. 그 전에는 추가 측정을 하지 않는다.
|
|
578
|
+
후보 채택과 발행은 계속 보류한다.
|
|
@@ -107,6 +107,16 @@ range, and says how it knows:
|
|
|
107
107
|
- The endpoint-only shortcut is **wrong** for a rotating long part: inside at both end angles, out in the middle.
|
|
108
108
|
That case takes the `bounded` row, not the `proven` one.
|
|
109
109
|
|
|
110
|
+
### The occupancy is the instance's box on a board (ADR-0087, amendment 2026-10-06)
|
|
111
|
+
|
|
112
|
+
- The reference size is the declared occupancy's `max − min` per axis, at the applied size and the resting state. The
|
|
113
|
+
board's 2D box is its XZ projection; the 3D object stands in it. A declared reach (a robot arm's 4700 mm) is the box.
|
|
114
|
+
- Every figure is resized. With `size.x/y/z` it is evaluated again with them (held to their range); without them it is
|
|
115
|
+
scaled in proportion, one scale on X, Y and Z. A proportional figure (no size inputs, or `sizing: 'uniform'`) keeps
|
|
116
|
+
its proportions, and shape, occupancy and box take the same scale.
|
|
117
|
+
- A state change never resizes the box: the occupancy is state-independent (§2–3), so the box does not follow the
|
|
118
|
+
drawing of the current pose.
|
|
119
|
+
|
|
110
120
|
## 4. Completion criterion for this contract
|
|
111
121
|
|
|
112
122
|
- The four providers' `@2` forms compile, evaluate, and their `@1` forms are unchanged (the operator-list guard
|