Phase 1 (#11): perf instrumentation — harness/perf.py + 3 hotspot wraps
신규 모듈 — harness/perf.py (54 LOC):
- perf_block(label) 컨텍스트 매니저 — 블록 단위 wall-clock + CPU 시간을 ms 단위로 측정.
- set_perf_log(sink) — 외부 sink 등록 (예: app.log). 등록 후 [PERF] 라인이 logger
외에도 그 sink 에 라우팅됨.
- 출력 형식: [PERF] {label}: wall={NN}ms cpu={NN}ms ({CPU|I/O/Net}-bound).
- cpu/wall > 0.5 면 CPU-bound 로 분류, 그 외 I/O/Net-bound (GIL 풀린 시간 비율).
Setup — scanvas_maker.py 2곳:
- import 블록 (~line 58): from harness.perf import perf_block, set_perf_log;
ImportError 시 contextlib.contextmanager 노옵 폴백 (모듈 누락 환경 대응).
- SCanvasApp.__init__ (~line 613): set_perf_log(self.log) 등록.
Hotspot wraps — scanvas_maker.py 3곳 (PERFORMANCE_BASELINE.md 매핑):
- TIN densify Phase C (line ~4430) → H3: with perf_block("TIN densify Phase C (10m→1m)").
- 위성 타일 다운로드 (line ~5384) → H1: with perf_block("위성 타일 다운로드+병합").
- 제어맵 캡처 x3 + composite (line ~5864) → H12: with perf_block("control map capture x3 + composite").
검증:
- python -m py_compile scanvas_maker.py harness/perf.py 통과.
- AST parse OK (39 top-level statements).
- ruff Green 정식 검증은 글로벌 ruff 설치 후 (uv pip install -e ".[dev]"; ruff check).
CHANGELOG.md 에 #11 perf instrumentation 항목 추가 (2026-05-08).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
43
CHANGELOG.md
43
CHANGELOG.md
@@ -10,6 +10,49 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-08
|
||||
|
||||
### [merge] Gitea s-canvas 원격(raw upload, 184185c)과 로컬 lint+Phase 0 history 통합
|
||||
|
||||
- **상황**: 원격 `https://gitea.hmac.kr/HYUNJUNGLEE/s-canvas.git` 에 사용자가 site에서 raw upload 한 1회 commit (`184185c`)이 존재. 로컬은 `53d8b53` → `b9342f6` (import + iter1~7 lint cleanup) → `e9cc6bf` (Phase 0 of expert feedback) history. **공통 조상 없음 (unrelated histories)**.
|
||||
- **분석**: 원격에만 있는 파일 0개 (원격은 로컬의 부분집합). 양쪽 다 있고 내용 다른 파일 29개 (raw upload vs lint-applied 버전). 로컬에만 있는 파일 80+ (Phase 0 산출물 + workspace/`_unused/` 등).
|
||||
- **전략**: `git merge --allow-unrelated-histories -X ours`. 충돌 시 로컬 우선으로 lint cleanup 보존.
|
||||
- **결과**: 머지 commit `8c6d7f0`. 자동 머지된 31파일 중 README.md 만 실질 변경 (로컬 0줄 → 원격 404줄 README 흡수 — 빈 파일 vs 내용있음은 `-X ours` 적용 외 단순 합병). 나머지 source/config 28개는 로컬 lint 버전 유지.
|
||||
- **푸시**: 머지 후 fast-forward push 가능. 원격 history 손실 없이 통합.
|
||||
|
||||
### [feat] #11 perf instrumentation — `harness/perf.py` 신규 + `scanvas_maker.py` 5곳 wire
|
||||
|
||||
- **사용자 피드백 #11**: "로딩이 오래 걸리는 부분(위성지도 결합·구조물 빌드 시 등)은 CPU 이용률이 대폭 증가하는 프로세스를 ms 단위로 추적해서 원인을 규명하고 최적화하는 조치 필요".
|
||||
|
||||
#### 신규 모듈 — `harness/perf.py` (54 LOC)
|
||||
- `perf_block(label)` — 컨텍스트 매니저. `with perf_block("XYZ tiles"): ...` 형태로 블록 실행 시간(wall + CPU)을 ms 단위로 측정.
|
||||
- `set_perf_log(callable)` — 외부 sink 등록 (예: `set_perf_log(app.log)` 시 GUI 로그 패널에도 표시).
|
||||
- 출력 형식: `[PERF] {label}: wall={NNN}ms cpu={NNN}ms ({CPU|I/O/Net}-bound)`. `cpu/wall > 0.5` 면 CPU-bound로 분류.
|
||||
|
||||
#### Wire 5곳 — `scanvas_maker.py` (commit `c94b4a7`)
|
||||
1. **import 블록 (~line 58)**: `from harness.perf import perf_block, set_perf_log` + ImportError 시 `@contextlib.contextmanager` 노옵 폴백 → `harness/` 모듈 누락 환경에서도 안전.
|
||||
2. **`SCanvasApp.__init__` (~line 613)**: `set_perf_log(self.log)` 등록 — perf 측정 라인이 GUI 텍스트박스에도 표시됨.
|
||||
3. **TIN densify Phase C (line ~4430)**: `with perf_block("TIN densify Phase C (10m→1m)")` 로 10단계 점진 격자 루프 감쌈 (PERFORMANCE_BASELINE.md H3).
|
||||
4. **위성 타일 다운로드 (line ~5384)**: `with perf_block("위성 타일 다운로드+병합")` 로 `_download_xyz_tiles()` 감쌈 — 사용자 피드백 #11이 명시한 "위성지도 결합" 핫스팟 (H1).
|
||||
5. **제어맵 캡처 파이프라인 (line ~5864)**: `with perf_block("control map capture x3 + composite")` 로 textured + depth + lineart 3-stage 캡처 + composite 감쌈 (H12).
|
||||
|
||||
#### 출력 예 (실제 측정 시)
|
||||
```
|
||||
[PERF] 위성 타일 다운로드+병합: wall=12340.5ms cpu=860.3ms (I/O/Net-bound)
|
||||
[PERF] TIN densify Phase C (10m→1m): wall=2150.7ms cpu=2080.4ms (CPU-bound)
|
||||
[PERF] control map capture x3 + composite: wall=4520.1ms cpu=3760.8ms (CPU-bound)
|
||||
```
|
||||
|
||||
#### 검증
|
||||
- `python -m py_compile scanvas_maker.py harness/perf.py` 통과. AST parse OK (39 top-level statements).
|
||||
- ruff Green 정식 검증은 다음 세션 (글로벌 ruff 미설치, `uv pip install -e ".[dev]"` 후 `ruff check`).
|
||||
|
||||
#### 다음 라운드 (#11 추가)
|
||||
- 사용자 실제 도면으로 [PERF] 출력 1회 측정 → PERFORMANCE_BASELINE.md "측정 후 비교 표" 채움.
|
||||
- 측정 결과 기반 추가 hotspot wrap (H7·H9·H13·H18 등) + 최적화 (numpy 벡터화 / 스레드 풀 / GIL 해제).
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-28
|
||||
|
||||
### [fix] 화면비 버튼이 텍스트만 떠서 안 보이는 문제 — vtkButtonWidget 로 교체
|
||||
|
||||
65
harness/perf.py
Normal file
65
harness/perf.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""S-CANVAS perf instrumentation — ms 단위 wall/CPU 시간 측정.
|
||||
|
||||
피드백 #11: "로딩이 오래 걸리는 부분(위성지도 결합·구조물 빌드 시 등)은
|
||||
CPU 이용률이 대폭 증가하는 프로세스를 ms 단위로 추적해서 원인을 규명하고
|
||||
최적화하는 조치 필요"
|
||||
|
||||
사용:
|
||||
from harness.perf import perf_block, set_perf_log
|
||||
|
||||
set_perf_log(app.log) # GUI 로그에 함께 기록 (옵션)
|
||||
|
||||
with perf_block("XYZ tiles 5x5"):
|
||||
download_tiles(...)
|
||||
|
||||
출력:
|
||||
[PERF] XYZ tiles 5x5: wall=2540.3ms cpu=120.1ms (I/O/Net-bound)
|
||||
|
||||
판별: cpu/wall > 0.5 → CPU-bound, 그 외 → I/O/Net-bound (GIL 풀린 시간 비율).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from contextlib import contextmanager
|
||||
from typing import Optional
|
||||
|
||||
_log_callable: Optional[Callable[[str], None]] = None
|
||||
_logger = logging.getLogger("scanvas.perf")
|
||||
|
||||
|
||||
def set_perf_log(fn: Callable[[str], None] | None) -> None:
|
||||
"""app.log 등 외부 sink로 perf 라인 라우팅. None이면 logger 만."""
|
||||
global _log_callable # noqa: PLW0603 (module-level singleton)
|
||||
_log_callable = fn
|
||||
|
||||
|
||||
def _emit(line: str) -> None:
|
||||
_logger.info(line)
|
||||
if _log_callable is not None:
|
||||
try:
|
||||
_log_callable(line)
|
||||
except Exception: # noqa: BLE001 (로그 sink 실패가 측정 흐름을 끊으면 안 됨)
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def perf_block(label: str):
|
||||
"""블록 단위 wall-clock + CPU 시간을 한 줄로 출력.
|
||||
|
||||
Args:
|
||||
label: 출력 prefix (예: "TIN densify Phase C", "capture x3").
|
||||
|
||||
측정 단위는 ms. CPU-bound vs I/O/Net-bound를 cpu/wall 비율로 거칠게 분류.
|
||||
"""
|
||||
t_wall = time.perf_counter()
|
||||
t_cpu = time.process_time()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
dt_wall = (time.perf_counter() - t_wall) * 1000
|
||||
dt_cpu = (time.process_time() - t_cpu) * 1000
|
||||
ratio = dt_cpu / dt_wall if dt_wall > 1e-3 else 0.0
|
||||
kind = "CPU" if ratio > 0.5 else "I/O/Net"
|
||||
_emit(f"[PERF] {label}: wall={dt_wall:.1f}ms cpu={dt_cpu:.1f}ms ({kind}-bound)")
|
||||
@@ -55,6 +55,16 @@ try:
|
||||
except ImportError:
|
||||
HARNESS_AVAILABLE = False
|
||||
|
||||
# Perf instrumentation (#11) — ms 단위 wall/CPU 측정. import 실패 시 no-op 폴백.
|
||||
try:
|
||||
from harness.perf import perf_block, set_perf_log
|
||||
except ImportError:
|
||||
@contextlib.contextmanager
|
||||
def perf_block(label): # type: ignore[no-redef]
|
||||
yield
|
||||
def set_perf_log(fn): # type: ignore[no-redef]
|
||||
pass
|
||||
|
||||
# 구조물 상세도면 치수 파서
|
||||
try:
|
||||
from detail_parser import DetailParser, dimensions_to_structure_params
|
||||
@@ -610,6 +620,10 @@ class SCanvasApp(ctk.CTk):
|
||||
|
||||
self.log("S-CANVAS Generative Design Engine 구동 완료.")
|
||||
|
||||
# Perf 측정 라인을 GUI 로그에도 함께 표시 (#11). harness/perf.py 폴백 import 시
|
||||
# set_perf_log는 no-op이라 실패해도 안전.
|
||||
set_perf_log(self.log)
|
||||
|
||||
def create_sidebar_button(self, text, command, row, **kwargs):
|
||||
btn = ctk.CTkButton(
|
||||
self.sidebar_frame, text=text, command=command, height=34, **kwargs)
|
||||
@@ -4427,6 +4441,7 @@ class SCanvasApp(ctk.CTk):
|
||||
from matplotlib.path import Path as _MplPath
|
||||
total_phase_c = 0
|
||||
steps_log = []
|
||||
with perf_block("TIN densify Phase C (10m→1m)"):
|
||||
for _step in (10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0):
|
||||
try:
|
||||
hull_c = _ConvexHull(pts[:, :2])
|
||||
@@ -5380,6 +5395,7 @@ class SCanvasApp(ctk.CTk):
|
||||
if not vk:
|
||||
raise ValueError("Vworld 타일 사용 시 API Key가 필요합니다. 사이드바에 입력해주세요.")
|
||||
tile_url_template = tile_url_template.replace("{vworld_key}", vk)
|
||||
with perf_block("위성 타일 다운로드+병합"):
|
||||
satellite_img = self._download_xyz_tiles(tile_url_template, min_lat, min_lon, max_lat, max_lon)
|
||||
|
||||
img_path = "satellite_temp.png"
|
||||
@@ -5861,6 +5877,7 @@ class SCanvasApp(ctk.CTk):
|
||||
ar_label = f"비율 {ar[0]}:{ar[1]}" if ar else f"뷰어 창 {self._saved_window_size or '미저장'}"
|
||||
self.log(f" 캡처 해상도: {out_w}x{out_h} ({ar_label} 기반)")
|
||||
|
||||
with perf_block("control map capture x3 + composite"):
|
||||
# 1. 위성 텍스처 3D 캡처
|
||||
self.capture_image = self._capture_from_camera(out_w, out_h, textured=True)
|
||||
self.capture_image.save("capture_textured.png")
|
||||
|
||||
Reference in New Issue
Block a user