From 5ce31e8614e47e564cd7b3e58f227427a30a7def Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:47:36 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[O(N^2)=20=EB=B3=91?= =?UTF-8?q?=EB=AA=A9=20=EA=B0=9C=EC=84=A0]=20SARIF=20=EB=B3=80=ED=99=98=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SARIF 변환 시 매번 list(rules).index(rule_id)를 호출하여 발생하는 O(N^2) 성능 병목을 해결하기 위해 별도의 dictionary를 두어 O(1) 조회로 최적화하였습니다. --- .jules/bolt.md | 4 ++++ appguardrail_core/sarif.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 8ce7ce8f7..caae7e809 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -77,3 +77,7 @@ ## 2024-11-20 - Optimize multiple tuple generation from a single collection **Learning:** `build_rule_metadata` derives exactly two collections, `owasp` and `cwe`, from the same references. Replacing its two generator traversals with one explicit loop reduces element visits from about 2N to N. Both versions remain O(N), so this is a constant-factor optimization rather than an asymptotic complexity improvement. **Action:** Combine repeated traversal when fixed derived collections share one source, while preserving ordering and classification semantics. Benchmark the production hot path before claiming a material wall-clock improvement. + +## 2024-12-10 - O(N^2) Performance from list(dict).index(key) +**Learning:** Using `list(dict).index(key)` inside loops introduces significant performance overhead, effectively transforming an O(1) dictionary key check into an O(N^2) bottleneck. This requires creating a new list allocation and scanning it linearly for every loop iteration. +**Action:** When determining the insertion index or position of dictionary keys during list processing, avoid generating lists. Track indices dynamically in a separate companion dictionary mapping the key to its incremental sequence length or insertion order index (e.g. `rule_indices[rule_id] = len(rules)`) for an O(1) constant-time lookup. diff --git a/appguardrail_core/sarif.py b/appguardrail_core/sarif.py index ca4a9fb6b..8f0d9ddd1 100644 --- a/appguardrail_core/sarif.py +++ b/appguardrail_core/sarif.py @@ -39,12 +39,14 @@ def findings_to_sarif( normalized = normalize_findings(findings) rules: dict[str, dict[str, Any]] = {} + rule_indices: dict[str, int] = {} results: list[dict[str, Any]] = [] for f in normalized: rule_id = f["rule_id"] severity = f["severity"] refs = f.get("references") or () if rule_id not in rules: + rule_idx = len(rules) rule: dict[str, Any] = { "id": rule_id, "name": rule_id, @@ -64,11 +66,14 @@ def findings_to_sarif( }, } rules[rule_id] = rule + rule_indices[rule_id] = rule_idx + else: + rule_idx = rule_indices[rule_id] results.append( { "ruleId": rule_id, - "ruleIndex": list(rules).index(rule_id), + "ruleIndex": rule_idx, "level": _LEVEL.get(severity, "note"), "message": {"text": f["message"].strip()}, "locations": [ From d539c90a3ffd539d286bb1ea02bab1e66312da2f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:00:26 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[O(N^2)=20=EB=B3=91?= =?UTF-8?q?=EB=AA=A9=20=EA=B0=9C=EC=84=A0]=20SARIF=20=EB=B3=80=ED=99=98=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SARIF 변환 시 매번 list(rules).index(rule_id)를 호출하여 발생하는 O(N^2) 성능 병목을 해결하기 위해 별도의 dictionary를 두어 O(1) 조회로 최적화하였습니다.