# Auto-Search Reference

## Profiling Tools

```bash
# Function-level profiling with cProfile:
$RUNNER -m cProfile -s cumtime -m pytest <test_file> -k "TESTFILTER" -v 2>&1 | head -50

# Save profile for programmatic analysis:
$RUNNER -m cProfile -o /tmp/profile.prof -m pytest <test_file> -k "TESTFILTER" -v
$RUNNER -c "
import pstats
p = pstats.Stats('/tmp/profile.prof')
p.sort_stats('cumtime')
p.print_stats(30)
"

# Line-level profiling (requires line_profiler):
# Add @profile decorator to target function, then:
kernprof -l -v target_script.py

# Static analysis for antipatterns — use Grep to scan for:
#   list.pop(0), deepcopy in loops, membership on lists, setdefault, missing __slots__, string concat

# Bytecode specialization analysis (Python 3.11+ only):
# Run AFTER identifying hot modules via cProfile
$RUNNER -c "import dis; dis.dis(open('<hot_module.py>').read())"
# Or programmatically: import the module, then dis.dis(module.function)
```

Adapt the profiling commands for the project's specific setup (virtualenv, PYTHONPATH, working directory, etc.).

## Micro-Benchmark Template

> See `../shared/micro-benchmark.md` for the A/B pattern, runner commands, and keep criteria.

**CPU-specific**: Use `timeit` inside the script for timing. **Keep threshold**: >20% or >2x, hot path confirmed by cProfile.

## Antipattern Catalog

All patterns are tagged: **[algorithmic]** = always relevant, **[version-sensitive]** = check Python version.

### HIGH impact

**[algorithmic] list.pop(0) / list.insert(0, x)** — O(n) per operation
- Replace with: `collections.deque` — O(1) popleft/appendleft
- Typical speedup: 10-100x on large lists

**[algorithmic] Membership test on list in loop** — O(n) per `in` check, O(n*m) total
- Replace with: `set` or `frozenset` — O(1) per lookup
- Typical speedup: 10-1000x depending on sizes
- Crossover point: set wins at ~4-8 elements

**[algorithmic] Nested loop for matching/lookup** — O(n*m)
- Replace with: Build dict/set index first, then single-pass lookup — O(n+m)
- Typical speedup: proportional to data size

**[algorithmic] Repeated linear search** — O(n) per search
- Replace with: `dict` for exact match, `bisect` for sorted data — O(1) or O(log n)
- Typical speedup: proportional to number of searches * data size

**[algorithmic] `@cache` / `@lru_cache` on instance methods** — memory leak
- `self` captured as cache key — instances can never be GC'd
- Replace with: module-level cache, instance-level cache, or `WeakValueDictionary`
- Impact: unbounded memory growth in long-running processes

**[algorithmic] `copy.deepcopy()` in hot path** — 10-100x slower than alternatives
- Replace with: shallow copy (`copy.copy`), direct construction, or avoid copying
- Only needed when nested mutable objects will be mutated independently

### MEDIUM impact

**[algorithmic] Missing `__slots__` on high-instance-count classes**
- Each instance without `__slots__` carries ~100+ bytes of `__dict__` overhead
- Replace with: `__slots__` or `@dataclass(slots=True)` (3.10+)
- Typical savings: ~50% memory per instance, ~280 MB saved on 1M instances of a 3-attribute class

**[algorithmic] String concatenation in loop** — potentially O(n^2)
- Replace with: `list.append()` + `''.join()` — O(n) total
- Note: CPython has a refcount-1 in-place optimization (since ~2004) that makes `+=` amortized O(n) when the string has one reference. However, `.join()` is O(n) by design across all implementations.
- Typical speedup: 2-50x on large strings (more when CPython optimization doesn't apply)

**[algorithmic] Repeated sorting** — O(n log n) per sort
- Replace with: `heapq` for top-k, `bisect.insort` for maintained order, sort once
- Typical speedup: depends on frequency

**[algorithmic] Manual counting/grouping** — verbose and error-prone
- Replace with: `collections.Counter`, `collections.defaultdict(list)`, `itertools.groupby`
- Typical speedup: 1.5-3x + cleaner code

**[algorithmic] Using dict where namedtuple/dataclass would be better** — hash overhead, typo-prone keys
- Replace with: `typing.NamedTuple` or `@dataclass` — faster attribute access, type safety
- Typical speedup: 1.5-2x for attribute access, significant memory savings

**[algorithmic] Unbounded `@cache` on long-running processes**
- `@cache` (alias for `lru_cache(maxsize=None)`) never evicts
- Replace with: `@lru_cache(maxsize=N)` with appropriate bound
- Impact: silent memory growth on servers/daemons

**[algorithmic] dict/set iteration after heavy deletion** — hidden O(max_historical_size)
- After deleting many keys, the hash table retains oversized allocation
- Fix: rebuild with `d = dict(d)` to compact

### LOW impact (only if on confirmed hot path)

**[version-sensitive] `operator.itemgetter/attrgetter` vs lambda** (<=3.10: ~30-50% faster; 3.11+: ~15-30%)
- C-implemented callable avoids Python frame creation
- Still a win on all versions, but diminished on 3.11+ (PEP 659)

**[version-sensitive] Local variable hoisting** (<=3.10: ~15-30%; 3.11+: ~5-10%)
- `append = result.append` before tight loops
- PEP 659 caches LOAD_ATTR, reducing the gain
- Only recommend for chained access like `self.data.append` on 3.11+

**[version-sensitive] `map(c_func)` vs list comprehension** (<=3.11: map faster; 3.12+: negligible)
- PEP 709 inlined comprehensions in 3.12, closing most of the gap
- Prefer comprehensions for readability on 3.12+

**[algorithmic] list comprehension vs generator** — list materializes all elements
- Replace with: generator expression when only iterating once
- Typical impact: memory reduction, marginal speed difference

**Unnecessary copies** — `list(x)` or `dict(x)` when original is not mutated
- Replace with: use original directly
- Typical impact: saves one copy operation

## Data Size Thresholds

Not all antipatterns matter at all sizes. Use these rough thresholds:

| Pattern | Worth fixing when n > |
|---------|----------------------|
| list.pop(0) | ~100 elements |
| list membership test | ~4-8 elements (set crossover) |
| Nested loop matching | ~100 elements per side |
| String concat in loop | ~100 iterations (or when CPython refcount-1 optimization may not apply) |
| Repeated sort | ~1000 elements or >10 sorts |
| Manual counting | Always (cleaner code regardless) |
| Missing `__slots__` | ~1000+ instances |
| `@cache` on method | Always (memory leak) |
| `copy.deepcopy()` | Always in loops; profile if called occasionally |
