4232907ab9
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 18s
CI / build (pull_request) Successful in 23s
CI / typecheck (pull_request) Successful in 34s
CI / security (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 3m17s
CI / docker (pull_request) Successful in 40s
CI / integration_tests (pull_request) Successful in 4m3s
CI / coverage (pull_request) Successful in 4m9s
CI / lint (push) Successful in 12s
CI / quality (push) Successful in 16s
CI / build (push) Successful in 15s
CI / security (push) Successful in 29s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m23s
CI / integration_tests (push) Successful in 2m48s
CI / docker (push) Successful in 38s
CI / coverage (push) Successful in 4m16s
CI / benchmark-publish (push) Successful in 16m55s
CI / benchmark-regression (pull_request) Successful in 30m14s
Add hierarchical decomposition with 4+ levels and bounded context per subplan. Implement decomposition heuristics (max_files_per_subplan, max_tokens_per_subplan, language/dir clustering). Add dependency closure computation for large graphs and DAG execution ordering. Add bounded dependency closure with cutoff thresholds and memoization for 10K+ files. Record decomposition decisions in DecisionService (strategy_choice + subplan_spawn entries). New modules: - decomposition_models.py: DecompositionConfig, DecompositionNode, DecompositionResult, DependencyEdge, DependencyGraph - decomposition_clustering.py: ClusteringStrategy with directory, language, and size clustering plus deterministic sort - decomposition_graph.py: DependencyClosureComputer with bounded closure, topological sort, cycle detection, and memoization - decomposition_service.py: DecompositionService orchestrating hierarchy building and decision recording Settings: planner_max_depth, planner_max_files_per_subplan, planner_max_tokens_per_subplan, planner_min_files_per_subplan Closes #205
131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
"""ASV benchmarks for large-project decomposition overhead.
|
|
|
|
Measures the time to decompose file sets of various sizes and to
|
|
compute dependency closures on synthetic graphs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
# Ensure the local *source* tree is importable even when ASV has an
|
|
# older build of the package installed.
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
import itertools # noqa: E402
|
|
|
|
from cleveragents.application.services.decomposition_graph import ( # noqa: E402
|
|
DependencyClosureComputer,
|
|
)
|
|
from cleveragents.application.services.decomposition_models import ( # noqa: E402
|
|
DecompositionConfig,
|
|
DependencyEdge,
|
|
DependencyGraph,
|
|
DependencyType,
|
|
)
|
|
from cleveragents.application.services.decomposition_service import ( # noqa: E402
|
|
DecompositionService,
|
|
)
|
|
|
|
|
|
def _build_files(tmpdir: str, count: int, levels: int = 3) -> list[str]:
|
|
"""Generate *count* files in *tmpdir* across *levels* dirs."""
|
|
paths: list[str] = []
|
|
for i in range(count):
|
|
level = i % levels
|
|
parts = [f"level{d}" for d in range(level + 1)]
|
|
dirpath = os.path.join(tmpdir, *parts)
|
|
os.makedirs(dirpath, exist_ok=True)
|
|
fpath = os.path.join(dirpath, f"file_{i:06d}.py")
|
|
with open(fpath, "w") as fh:
|
|
fh.write("x" * 200)
|
|
paths.append(fpath)
|
|
return sorted(paths)
|
|
|
|
|
|
def _build_chain_graph(size: int) -> DependencyGraph:
|
|
"""Build a chain of *size* nodes."""
|
|
g = DependencyGraph()
|
|
nodes = [f"file_{i:06d}.py" for i in range(size)]
|
|
for n in nodes:
|
|
g.add_node(n)
|
|
for src, tgt in itertools.pairwise(nodes):
|
|
g.add_edge(DependencyEdge(src, tgt, DependencyType.IMPORT))
|
|
return g, nodes # type: ignore[return-value]
|
|
|
|
|
|
class DecomposeSmallSuite:
|
|
"""Benchmark decomposition of a small file set (100 files)."""
|
|
|
|
def setup(self) -> None:
|
|
self._tmpdir = tempfile.mkdtemp(prefix="bench-decompose-")
|
|
self._files = _build_files(self._tmpdir, 100)
|
|
self._svc = DecompositionService()
|
|
|
|
def time_decompose_default(self) -> None:
|
|
"""Decompose 100 files with default config."""
|
|
self._svc.decompose(self._files)
|
|
|
|
|
|
class DecomposeMediumSuite:
|
|
"""Benchmark decomposition of a medium file set (1000 files)."""
|
|
|
|
def setup(self) -> None:
|
|
self._tmpdir = tempfile.mkdtemp(prefix="bench-decompose-")
|
|
self._files = _build_files(self._tmpdir, 1000)
|
|
self._svc = DecompositionService()
|
|
|
|
def time_decompose_default(self) -> None:
|
|
"""Decompose 1000 files with default config."""
|
|
self._svc.decompose(self._files)
|
|
|
|
|
|
class DecomposeLargeSuite:
|
|
"""Benchmark decomposition of a large file set (5000 files)."""
|
|
|
|
def setup(self) -> None:
|
|
self._tmpdir = tempfile.mkdtemp(prefix="bench-decompose-")
|
|
self._files = _build_files(self._tmpdir, 5000, levels=5)
|
|
self._svc = DecompositionService()
|
|
self._config = DecompositionConfig(max_files_per_subplan=200)
|
|
|
|
def time_decompose_custom(self) -> None:
|
|
"""Decompose 5000 files with custom config."""
|
|
self._svc.decompose(self._files, self._config)
|
|
|
|
|
|
class ClosureSuite:
|
|
"""Benchmark dependency closure computation."""
|
|
|
|
def setup(self) -> None:
|
|
result = _build_chain_graph(1000)
|
|
self._graph: DependencyGraph = result[0] # type: ignore[assignment]
|
|
self._roots: list[str] = [result[1][0]] # type: ignore[index]
|
|
self._computer = DependencyClosureComputer()
|
|
|
|
def time_closure_unbounded(self) -> None:
|
|
"""Compute unbounded closure on a 1000-node chain."""
|
|
self._computer.clear_cache()
|
|
self._computer.compute_closure(self._graph, self._roots)
|
|
|
|
def time_closure_bounded(self) -> None:
|
|
"""Compute bounded closure (cutoff=100) on a 1000-node chain."""
|
|
self._computer.clear_cache()
|
|
self._computer.compute_closure(self._graph, self._roots, cutoff=100)
|
|
|
|
def time_closure_memoized(self) -> None:
|
|
"""Compute closure twice — second should be memoized."""
|
|
self._computer.clear_cache()
|
|
self._computer.compute_closure(self._graph, self._roots)
|
|
self._computer.compute_closure(self._graph, self._roots)
|