Skip to content

Commit ae07c40

Browse files
author
Bounty Hunter
committed
Add performance optimization documentation
Add comprehensive documentation explaining the trace callback performance optimizations implemented for Issue #115. Includes: - Summary of changes - Technical details - Performance impact analysis - Future work suggestions - Backward compatibility notes
1 parent df75093 commit ae07c40

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

PERFORMANCE_OPTIMIZATIONS.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# CrossHair Issue #115: Trace Callback Performance Optimization
2+
3+
## Summary
4+
5+
This implementation addresses [Issue #115](https://github.com/pschanely/CrossHair/issues/115) by optimizing the trace callback performance in CrossHair. The issue noted that CrossHair spends a lot of execution time in pure-Python `sys.settrace` handlers, and moving more logic to C could significantly speed up CrossHair.
6+
7+
## Changes Made
8+
9+
### 1. C Extension Optimizations (`crosshair/_tracers.c`)
10+
11+
#### Opcode-to-Handler Caching
12+
- **Problem**: For every traced opcode, the C code would iterate through all handler tables and call into Python, even when there's only one handler.
13+
- **Solution**: Added a simple cache that remembers the last opcode and its handler for single-handler scenarios.
14+
- **Implementation**:
15+
- Added `last_opcode` and `last_handler` fields to the CTracer struct
16+
- When there's only one handler table, check the cache first before iterating
17+
- Cache is invalidated when modules are pushed/popped
18+
19+
#### Streamlined Post-Op Callback Processing
20+
- **Problem**: Post-op callback processing had nested conditionals and multiple exit points.
21+
- **Solution**: Refactored into a separate inline function `process_postop_callbacks()` with early returns for the common no-callback case.
22+
23+
#### Cache Statistics
24+
- Added `cache_hits` and `cache_misses` counters for debugging
25+
- Added `get_cache_stats()` method to expose statistics to Python
26+
- Helps verify that optimizations are working effectively
27+
28+
#### Handler Table Iteration Order
29+
- **Optimization**: Changed from forward iteration to reverse iteration
30+
- **Rationale**: More recently added handlers are likely to be more active; reverse iteration improves cache locality
31+
32+
### 2. Header File Updates (`crosshair/_tracers.h`)
33+
34+
Added new fields to the `CTracer` struct:
35+
```c
36+
int last_opcode; /* Cache: last opcode processed */
37+
PyObject* last_handler; /* Cache: handler for last_opcode */
38+
long cache_hits; /* Statistics: cache hits */
39+
long cache_misses; /* Statistics: cache misses */
40+
```
41+
42+
### 3. Python-Level Optimizations (`crosshair/tracers.py`)
43+
44+
#### Fast-Path for Primitive Types
45+
- **Problem**: The `trace_op` method performs expensive attribute lookups for every target, even for primitive types that will never need tracing.
46+
- **Solution**: Added an early exit check for common primitive types:
47+
```python
48+
fast_path_types = (type(None), int, float, str, bool, list, dict, tuple, set)
49+
if target_type in fast_path_types:
50+
return None
51+
```
52+
53+
#### Optimized Exception Handling
54+
- **Problem**: Stack reads were wrapped in try/except blocks that always ran.
55+
- **Solution**: Combined operations that can fail into single try/except blocks to reduce overhead.
56+
57+
#### Inline Attribute Lookups
58+
- **Problem**: Multiple `__getattribute__` calls for the same object.
59+
- **Solution**: Inlined the lookups to reduce function call overhead.
60+
61+
### 4. Testing
62+
63+
Created two test files:
64+
65+
#### `tracers_performance_test.py`
66+
- Unit tests for the new fast-path behavior
67+
- Tests for cache statistics functionality
68+
- Ensures optimizations don't break existing functionality
69+
70+
#### `tracers_performance_benchmark.py`
71+
- Benchmarks for various tracing scenarios
72+
- Measures cache hit rates
73+
- Demonstrates the impact of optimizations
74+
75+
### 5. Documentation
76+
77+
#### Updated `doc/source/changelog.rst`
78+
- Added entry for the next version documenting the performance improvements
79+
80+
#### Added inline documentation
81+
- C code includes detailed comments explaining the optimizations
82+
- Python docstrings explain the fast-path behavior
83+
84+
## Performance Impact
85+
86+
### Expected Improvements
87+
88+
1. **Reduced Python-to-C Transitions**: The opcode-to-handler cache avoids Python function calls for the common single-handler case.
89+
90+
2. **Fewer Attribute Lookups**: The primitive type fast-path in Python eliminates expensive `__getattribute__` calls for common types.
91+
92+
3. **Better Cache Locality**: Reverse iteration of handler tables improves CPU cache utilization.
93+
94+
4. **Streamlined Processing**: The refactored post-op callback processing has fewer branches and better branch prediction.
95+
96+
### Benchmarking
97+
98+
Run the benchmark script to measure improvements:
99+
```bash
100+
python crosshair/tracers_performance_benchmark.py
101+
```
102+
103+
The benchmark tests:
104+
- Simple function calls
105+
- Nested function calls
106+
- Method calls
107+
- Loops with function calls
108+
109+
## Technical Details
110+
111+
### Cache Invalidation Strategy
112+
113+
The opcode-to-handler cache is invalidated when:
114+
1. A new module is pushed (`CTracer_push_module`)
115+
2. A module is popped (`CTracer_pop_module`)
116+
117+
This ensures that the cache never contains stale data while minimizing invalidation overhead.
118+
119+
### Thread Safety Considerations
120+
121+
The cache is per-CTracer instance, and CTracer instances are not shared between threads. The existing thread safety model of CrossHair is maintained.
122+
123+
### Backward Compatibility
124+
125+
All changes are backward compatible:
126+
- The `get_cache_stats()` method is new and doesn't affect existing code
127+
- The fast-path optimizations are transparent to callers
128+
- All existing tests should continue to pass
129+
130+
## Future Work
131+
132+
Potential additional optimizations:
133+
134+
1. **More Sophisticated Caching**: Could implement an LRU cache for multiple handlers
135+
2. **Fast-Path for Common Patterns**: Identify and optimize common opcode sequences
136+
3. **Batch Processing**: Group multiple opcodes before calling into Python
137+
4. **Profile-Guided Optimization**: Use cache stats to identify the most common paths and optimize them further
138+
139+
## Files Changed
140+
141+
1. `crosshair/_tracers.c` - Core C extension optimizations
142+
2. `crosshair/_tracers.h` - Header file with new struct fields
143+
3. `crosshair/tracers.py` - Python-level fast-path optimizations
144+
4. `doc/source/changelog.rst` - Documentation update
145+
5. `crosshair/tracers_performance_test.py` - New test file
146+
6. `crosshair/tracers_performance_benchmark.py` - New benchmark file
147+
148+
## References
149+
150+
- Issue: https://github.com/pschanely/CrossHair/issues/115
151+
- Discussion: https://github.com/pschanely/CrossHair/discussions/105#discussioncomment-662381

0 commit comments

Comments
 (0)