perf(B09 [저장] · 708-1): 같은 입력 되풀이 셈 묶음 — 화면 [저장] 3.7 s → 1.7 s · 결과 바이트 같음

- M01 캐시 `steady()` 범위 — 요청 한 번 안 파일 자국 한 번(77 번 × 19 ms → 1 번) · stale · forget 뒤 다시 · 범위 밖 그대로
- 두 번 셈(183-1) 2차 산근 = 1차가 본 단가(있음 · 값)가 같을 때만 1차 풀이 사본 · 다르면 다시 셈
- [저장] 단가 수요 한 번(셈 답에 넘김) · 단계 표시 = 셈과 함께
- B08 `once()` 범위 — B09 원천 읽기 때 토공 채집석 공제가 자재 집계표 답을 다시 씀(두 번 셈 → 한 번)
- 시험: test_b09_save_speed 새로 · test_183 바꿔 끼운 셈 함수 = 새 memo 인자 받음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 19:34:44 +09:00
co-authored by Claude Opus 5.5
parent 01334a5730
commit 3bd3bcc5a8
6 changed files with 256 additions and 33 deletions
+29 -1
View File
@@ -10,6 +10,8 @@
from __future__ import annotations
import contextlib
import contextvars
import json
import sys
import threading
@@ -28,6 +30,28 @@ _INDEX: dict = {} # 폴더 · 자국 · files · whole
_BLOCK: dict[str, tuple[tuple, dict]] = {} # 로직 파일 → (자국, {키: `_check` 가 센 것})
_BASE: dict[str, tuple] = {} # 막힘을 셀 때 바탕이던, 로직 아닌 파일의 자국
_MARKED: dict[str, set] = {} # 우리가 고친 요소·표 파일 → 흔들린 자리(`narrow` 가 고른 것)
#: `steady()` 범위 — 폴더 → 자국(범위 안에선 자국 한 번 · `to_thread` 도 같은 묶음)
_STEADY: contextvars.ContextVar[dict | None] = contextvars.ContextVar("m01_steady", default=None)
@contextlib.contextmanager
def steady():
"""요청 한 번처럼 마스터가 안 바뀌는 범위 — `loaded` 가 파일 자국을 한 번만 셈."""
token = _STEADY.set({}) if _STEADY.get() is None else None
try:
yield
finally:
if token is not None:
_STEADY.reset(token)
def _now(folder: Path) -> dict[str, tuple]:
held = _STEADY.get()
if held is None:
return _stamps(folder)
if folder not in held:
held[folder] = _stamps(folder)
return held[folder]
def _stamps(folder: Path) -> dict[str, tuple]:
@@ -84,6 +108,8 @@ def forget() -> None:
_BLOCK.clear()
_BASE.clear()
_MARKED.clear()
if (held := _STEADY.get()) is not None:
held.clear()
def stale(names, marks=()) -> None:
@@ -97,6 +123,8 @@ def stale(names, marks=()) -> None:
if was.get("그룹") != "로직":
_MARKED.setdefault(name, set()).update(marks)
_INDEX.pop("자국", None)
if (held := _STEADY.get()) is not None:
held.clear()
def loaded(folder: Path) -> tuple[dict[str, dict], object]:
@@ -105,7 +133,7 @@ def loaded(folder: Path) -> tuple[dict[str, dict], object]:
if _INDEX.get("폴더") != folder: # 시험은 사본 폴더로 바꿈
forget()
_INDEX["폴더"] = folder
now = _stamps(folder)
now = _now(folder)
for gone in [name for name in _READ if name not in now]:
del _READ[gone]
for name, stamp in now.items():