From 78e4c4d53ab80c94eba7abb2b3274690d840e992 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 12 Jul 2026 13:56:27 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20N+1=20subprocess=20?= =?UTF-8?q?=EB=B3=91=EB=AA=A9=EC=9D=84=20=ED=95=B4=EA=B2=B0=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20git=20diff=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=BA=90=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `scripts/ci/opencode_review_approve_gate.sh` 스크립트에 내장된 Python 코드 내에서 동일한 파일 경로에 대해 `changed_new_lines`가 여러 번 호출될 때마다 독립적인 `git diff` 서브프로세스를 생성하는 O(N) 성능 병목이 있었습니다. 이 커밋은 Python의 내장 모듈인 `functools.cache`를 사용하여 함수의 반환값을 메모이제이션하도록 변경합니다. 반환값을 `set`에서 불변성(immutable)과 해시 가능성(hashability)을 보장하는 `frozenset`으로 변경하여 캐싱이 안전하게 작동하도록 보장합니다. 이 최적화를 통해 다수의 finding이 동일한 변경 파일을 참조할 때 불필요한 프로세스 생성과 I/O 대기 시간을 획기적으로 줄일 수 있습니다. --- .jules/bolt.md | 3 +++ scripts/ci/opencode_review_approve_gate.sh | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 20bdfb8a..e7066a3a 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -37,3 +37,6 @@ ## 2024-07-25 - Pre-calculate and cache environmental file reads **Learning:** The `current_changed_files` function in `scripts/ci/opencode_review_normalize_output.py` was being called multiple times per item during JSON structure normalization, leading to redundant I/O reads of `OPENCODE_CHANGED_FILES_FILE`. **Action:** Use `@functools.lru_cache(maxsize=1)` and return an immutable `frozenset` when repeatedly reading static contextual files within a script's execution lifecycle. +## 2024-11-23 - Memoize File-Based Subprocess Queries in Embedded Python Scripts +**Learning:** Found an N+1 subprocess bottleneck in `scripts/ci/opencode_review_approve_gate.sh` where `changed_new_lines` invoked `git diff` for every finding, even when multiple findings pointed to the same file. Repeatedly shelling out inside loops is a severe performance anti-pattern. +**Action:** When validating multiple findings against the same file, decorate the inspection function with `@functools.cache` and ensure the return value is immutable (e.g., `frozenset` instead of `set`) to avoid redundant subprocess calls. diff --git a/scripts/ci/opencode_review_approve_gate.sh b/scripts/ci/opencode_review_approve_gate.sh index 03828be1..406c16bd 100755 --- a/scripts/ci/opencode_review_approve_gate.sh +++ b/scripts/ci/opencode_review_approve_gate.sh @@ -210,6 +210,7 @@ PR_HEAD_SHA_VAR="${PR_HEAD_SHA:-${HEAD_SHA:-}}" if ! python3 - "$SOURCE_ROOT" "$TMP_JSON" "$PR_BASE_SHA_VAR" "$PR_HEAD_SHA_VAR" <<'PY' from __future__ import annotations +import functools import json import re import subprocess @@ -231,9 +232,12 @@ def normalized_line(value: str) -> str: return " ".join(value.strip().split()) -def changed_new_lines(path_value: str) -> set[int]: +# ⚡ Bolt: Memoize changed_new_lines to prevent N+1 git diff subprocess calls +# Impact: Substantially reduces I/O wait overhead when multiple findings are on the same file path +@functools.cache +def changed_new_lines(path_value: str) -> frozenset[int]: if not pr_base_sha or not pr_head_sha: - return set() + return frozenset() try: completed = subprocess.run( [ @@ -255,9 +259,9 @@ def changed_new_lines(path_value: str) -> set[int]: shell=False, ) except OSError: - return set() + return frozenset() if completed.returncode not in {0, 1}: - return set() + return frozenset() line_numbers: set[int] = set() hunk_header = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@") @@ -270,7 +274,7 @@ def changed_new_lines(path_value: str) -> set[int]: if count <= 0: continue line_numbers.update(range(start, start + count)) - return line_numbers + return frozenset(line_numbers) _file_cache: dict[Path, list[str]] = {}