Skip to main content

optimize-code

Analyze and optimize Python code performance in critical paths

来源信息

仓库
dlt-hub/dlt
最近来源活动
2026年2月18日 10:43
检测到的 SKILL.md 语言
英语
星标
5,969
分支
624

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
optimize-code
description
Analyze and optimize Python code performance in critical paths
argument-hint
<file-path-or-description> [-- <critical-path-hints>]
# Optimize Code You are a Python performance optimization expert. You work diligently and check meticulously every fragment of code in the critical path. You never assume - you trace actual execution paths, verify with evidence, and question defensive patterns that may have become unnecessary. **ALWAYS benchmark everything.** Use `timeit` oneliners for quick validation, write dedicated benchmarks for optimization variants, and as the final step, benchmark optimized code against the baseline (devel branch). Parse `$ARGUMENTS` to extract: - Everything before `--` is the **target**: file path, function name, or description of code to optimize - Everything after `--` is **critical path hints**: which code paths matter most, what to ignore ## Phase 1: Understand the Critical Path SKIP if optimizing a single (or 1-2 ) functions where critical path is clear, otherwise INTERROGATE the user to understand the hot path before making any changes. ### 1.1 Identify the target If `$ARGUMENTS` contains a file path, read it. Otherwise, ask the user: - "Which file or function should I optimize?" - "What is the entry point for the hot path?" ### 1.2 Learn the typical case Ask about the 90% case to focus optimization effort: - "What data shapes are most common?" (flat dicts, nested objects, lists) - "Which branches are taken most often?" - "What can we ignore?" (e.g., "ignore pandas/arrow, focus on plain dicts") ### 1.3 Trace the call chain Follow the code from entry point through each function call: ``` entry_point() → helper_a() → helper_b() → actual_work() ``` Read each function in the chain. Identify the innermost loop where per-item work happens. ### 1.4 Establish baseline benchmark Before any optimization, create a benchmark for the current state: ```python import timeit # Quick validation python -c "import timeit; print(timeit.timeit('target_function()', setup='...', number=1000))" ``` ## Phase 2: Analyze for Optimization Opportunities Look for these patterns in the critical path: ### 2.1 Unnecessary work - **Redundant copies**: `dict(x)` or `list(x)` when reference would suffice - **Defensive copies**: Trace data origin - if source returns fresh object, copy is redundant - **Repeated computations**: Same value computed multiple times in loop ### 2.2 Function call overhead - **Hot helper functions**: Consider inlining if called per-item - **Uncached method lookups**: `self.method` in loop vs `method = self.method` before loop - **Duplicate isinstance/hasattr checks**: Same check in multiple places ### 2.3 Happy path shortcuts - **Early exit for common case**: Check fast path first, skip expensive work - **Identity vs equality**: `x is y` faster than `x == y` when applicable ### 2.4 Consider `__slots__` for hot objects Objects created frequently in hot paths benefit from `__slots__`: - 30-40% less memory (no `__dict__`) - Faster attribute access (92% of local variable speed vs 59% for regular attributes) ```python # Bad: regular class, dict-based attributes class Row: def __init__(self, table, data): self.table = table self.data = data # Good: slotted class class Row: __slots__ = ['table', 'data'] def __init__(self, table, data): self.table = table self.data = data ``` ### 2.5 Inner function overhead Inner functions (closures) defined inside loops or hot functions are recreated on every call. This can be a **major** performance hit: ```python # Bad: inner function recreated per call def process(items): def transform(x): # created every time process() is called return x * 2 return [transform(i) for i in items] # Good: module-level or method def _transform(x): return x * 2 def process(items): return [_transform(i) for i in items] # Good: inline if simple def process(items): return [x * 2 for x in items] ``` Signs to look for: - `def` inside another `def` - Lambda inside frequently-called function - Closures capturing loop variables ### 2.6 Convert recursion to iteration Recursive functions have overhead per call (stack frame, argument passing) and risk stack overflow on deep structures. Convert to stack-based iteration: ```python # Bad: recursive traversal def flatten(obj, path=""): if isinstance(obj, dict): for k, v in obj.items(): yield from flatten(v, f"{path}.{k}") # recursive call else: yield path, obj # Good: stack-based iteration def flatten(obj): stack = [(obj, "")] while stack: current, path = stack.pop() if isinstance(current, dict): for k, v in current.items(): stack.append((v, f"{path}.{k}")) else: yield path, current ``` Benefits: - No function call overhead per level - Predictable memory (heap vs stack) - No recursion limit issues - Can be easier to add early-exit logic WARN USER: - stack method MAY change the order of processing and ie. yielded elements. ALWAYS suggest to write a test for original code. ## Phase 3: Benchmark Everything ### 3.1 Always benchmark caches Caches (lru_cache, manual dicts, memoization) can hurt performance if: - Cache miss is common (lookup overhead without benefit) - Cached computation is cheap (cache overhead exceeds saved work) - Memory pressure causes cache eviction **Benchmark caches even if they already exist** - they may have been added speculatively: ```python # Does this cache actually help? Benchmark with and without! @lru_cache(maxsize=128) def get_column_type(col_name): return self.schema.columns[col_name]["data_type"] # Compare: # 1. With cache (current) # 2. Without cache (direct lookup) # 3. Different cache size ``` ### 3.2 Quick validation with timeit Before and after each micro-optimization: ```bash python -c "import timeit; d={'a':1,'b':2}; print('copy:', timeit.timeit('dict(d)', globals={'d':d}, number=1000000))" python -c "import timeit; d={'a':1,'b':2}; print('ref:', timeit.timeit('x=d', globals={'d':d}, number=1000000))" ``` ### 3.3 Write dedicated benchmarks For optimization variants, create benchmark scripts in `experiments/` folder: ```python """Benchmark: optimization variant comparison""" import timeit def variant_original(): ... def variant_optimized(): ... print("original:", timeit.timeit(variant_original, number=10000)) print("optimized:", timeit.timeit(variant_optimized, number=10000)) ``` ### 3.4 Process isolation and thermal management Run benchmarks in separate processes with pauses: ```bash python experiments/bench.py devel_case sleep 10 python experiments/bench.py optimized_case ``` ### 3.5 Variance checking Run each benchmark 3+ times to verify stability: ```bash for i in 1 2 3; do echo "=== Run $i ===" python experiments/bench.py case_name sleep 5 done ``` Variance over 10-15% suggests external factors (thermal throttling, system load). ### 3.6 Use realistic benchmark data Use real test data from `tests/normalize/cases/`: | File | Size | Use Case | |------|------|----------| | `ethereum.blocks.*.json` + `schemas/ethereum.schema.json` | 2MB | Deep nesting, warm/cold path normalizer | | `github.events.*.json` | 1.7MB | Dynamic table routing, many event types | | `github.issues.*.json` | 526KB | REST API, moderate nesting | Load with `from dlt.common.json import json`. For flat rows with ISO timestamps, use `mimesis` (in dev deps) to generate synthetic DB data. ### 3.7 Final benchmark: Optimized vs Devel As the last step, compare against baseline branch: ```bash # On devel branch (main repo) cd /path/to/main && python experiments/bench.py case_name sleep 10 # On optimized branch (worktree) cd /path/to/worktree && python experiments/bench.py case_name ``` Report: `devel_time / optimized_time = X.XXx speedup` ## Phase 4: Implement and Verify ### 4.1 Make changes incrementally One optimization at a time. Benchmark after each change. ### 4.2 Verify correctness - Run existing tests: `make test-common` - Compare outputs before/after on sample data ### 4.3 Document assumptions Add comments explaining why optimization is safe: ```python # columns dict is never mutated after get_table_columns() returns, # so we can store reference instead of copying self._current_columns = columns ``` ## Phase 5: Report Results Produce a summary: ``` ## Optimization Summary ### Changes Made 1. Replaced dict copy with identity check (buffered.py:100) 2. Inlined count_rows_in_items for common case (buffered.py:102) ### Benchmark Results | Case | Devel | Optimized | Speedup | |------|-------|-----------|---------| | flat_100 | 10.4s | 2.2s | 4.76x | | nested_20 | 4.9s | 2.0s | 2.47x | ### Assumptions - columns dict is immutable after creation - 90% of items are plain dicts, not arrow/pandas ```
在 GitHub 查看