# Optimization Session Handoff

**Session ID**: codeflash-20260413-01
**Domain**: Deep (cross-domain analysis)
**Branch**: codeflash/optimize
**Start time**: 2026-04-13

## Project Context

**Project**: Odoo ERP Framework
- Large Python-based ERP system
- Uses Pillow for image processing
- Performance-critical: image resizing, format conversion, thumbnail generation
- Python 3.14.3, pip-based installation

## Baseline Profile

**Target**: Image processing operations (benchmark_image.py)
- Operations: resize 2048x2048→512x512, thumbnail generation, format conversion (PNG→JPEG→PNG), base64 encoding
- Iterations: 5x each operation

### CPU Profile Results

**Total runtime**: 5.481 seconds

**Top hotspots by cumulative time:**
1. `Image.resize()` - 3.27s (60%) 
   - `ImagingCore.resize` (C extension) - 1.86s actual work
2. `Image.save()` - 1.92s (35%)
   - `ImagingDecoder.decode` - 1.33s 
   - `ImagingEncoder.encode` - 0.64s
3. Module imports - 1.11s (20% - overlaps with above)
   - PIL module loading overhead

**Top hotspots by self time:**
1. `ImagingCore.resize` - 1.86s (C extension, not optimizable)
2. `ImagingDecoder.decode` - 1.33s (C extension, not optimizable)
3. `ImagingEncoder.encode` - 0.64s (C extension, not optimizable)
4. `PngImagePlugin._save` - 0.067s
5. `benchmark_image.create_test_image` - 0.060s
6. `Image.load` - 0.052s (566 calls)

### Memory Profile Results

**Peak memory**: 3.1 MiB
**Current memory**: 2.8 MiB

**Top allocations:**
1. importlib bootstrap - 939 KiB (module loading)
2. PIL PngImagePlugin - 55.6 KiB
3. PIL ImageFile - 41.3 KiB
4. benchmark code - 21.0 KiB

## Cross-Domain Analysis

### CPU ↔ Memory Interactions

**Import overhead (Structure × CPU × Memory)**
- 1.11s CPU time in module imports
- 939 KiB memory in importlib bootstrap
- All PIL image format plugins are eagerly loaded even if not used
- **Opportunity**: Defer imports of unused image formats

**Image object creation (CPU × Memory)**
- `create_test_image()` uses `putpixel()` in a loop (477 calls, 0.014s)
- Each putpixel call crosses Python↔C boundary
- **Opportunity**: Use array-based approaches or batch operations

**Image load calls (CPU)**
- 566 calls to `Image.load()` taking 0.052s total
- Many redundant load checks
- **Opportunity**: Cache loaded state or reduce redundant loads

### I/O Bottlenecks

**PIL I/O operations:**
- Encoding/decoding happens in C extensions (not optimizable directly)
- BUT: Multiple encode/decode cycles in format conversion
- **Opportunity**: Avoid unnecessary format round-trips

**Base64 operations:**
- Not profiled separately in this run
- Need deeper analysis

## Optimization Targets

### High-Priority (Cross-Domain)

1. **Defer PIL plugin imports** (Structure + CPU + Memory)
   - Current: All image format plugins load eagerly
   - Target: `Image.preinit()` call and plugin loading
   - Expected: -1.1s CPU, -939 KiB memory
   - Domain: Structure (import time optimization)

2. **Optimize test image creation** (CPU)
   - Current: 477 `putpixel()` calls, 0.060s total
   - Target: `create_test_image()` function
   - Use array/buffer approach instead of per-pixel calls
   - Expected: -0.040s CPU
   - Domain: CPU (algorithmic)

3. **Reduce redundant Image.load() calls** (CPU)
   - Current: 566 calls, 0.052s
   - Target: Image loading logic
   - Expected: -0.030s CPU
   - Domain: CPU (caching/memoization)

### Medium-Priority

4. **Optimize format conversion pipeline** (CPU)
   - Current: PNG→JPEG→PNG involves multiple encode/decode cycles
   - Investigate if intermediate formats can be avoided
   - Expected: -0.2s CPU
   - Domain: CPU (algorithmic)

5. **BytesIO buffer management** (Memory)
   - Multiple BytesIO objects created/destroyed
   - Investigate buffer reuse
   - Expected: Small memory win
   - Domain: Memory

### Out of Scope

- Core PIL C extensions (resize, encode, decode) - already optimized
- PNG compression algorithm - external library

## Next Steps

1. **Experiment 1**: Defer PIL plugin imports
   - Modify `benchmark_image.py` to selectively import only needed formats
   - Remove `Image.preinit()` call
   - Re-profile and measure impact

2. **Experiment 2**: Optimize image creation using numpy/array interface
   - Replace per-pixel `putpixel()` with array-based approach
   - Measure CPU improvement

3. **Experiment 3**: Investigate Image.load() redundancy
   - Add caching or state tracking to reduce load calls

## Key Discoveries

1. **PIL is already well-optimized**: Core operations are in C extensions and perform well
2. **Import overhead is significant**: 20% of runtime spent loading modules
3. **Python↔C boundary costs**: Per-pixel operations (putpixel) are expensive due to call overhead
4. **Low memory footprint**: PIL's C extensions keep memory usage minimal (3.1 MiB peak)

## Constraints

- Must maintain compatibility with PIL API
- Cannot modify PIL internals (external library)
- Must preserve image quality and correctness
- No guard command specified - will verify with manual inspection

## Experiment Log

### Experiment 1: Replace putpixel with numpy arrays (DISCARDED)

**Hypothesis**: Using numpy arrays for image creation would be faster than per-pixel PIL operations.

**Implementation**: Replaced `img.putpixel()` loop with `np.full()` + `Image.fromarray()`.

**Results**:
- CPU: 5.481s → 6.075s (+0.594s, **11% SLOWER**)
- Memory: 3.1 MiB → 13.5 MiB peak (**4.4x WORSE**)
- New bottleneck: `numpy.full()` at 0.615s

**Root cause**: 
- numpy creates large intermediate arrays (12.5 MB for 2048x2048x3)
- PIL's native C-level operations are already optimal
- The original putpixel loop only cost 0.014s (0.26% of runtime), not worth optimizing

**Decision**: DISCARD - This is a regression, not an optimization.

**Key learning**: "Obvious" optimizations can backfire. Always profile after changes. PIL's C extensions are highly optimized - avoid introducing Python/numpy overhead.

## Current Status

**Branch**: codeflash/optimize
**Experiments**: 1 attempted, 1 discarded
**Improvements**: None yet

**Analysis**: The target workload (PIL image operations) is dominated by C extension calls that cannot be optimized from Python:
- 60% of runtime: PIL C extension resize operation
- 35% of runtime: PIL C extension encode/decode operations
- Only 5% of runtime in Python code, mostly import overhead

**Conclusion**: This specific workload (image processing with PIL) has limited optimization potential because:
1. Core operations are already in optimized C extensions
2. The Python wrapper code is minimal
3. Import overhead could be reduced but requires structural changes to how PIL is loaded

**Recommendation**: For meaningful performance gains on Odoo, we should target different subsystems:
- Database query optimization (ORM operations)
- Business logic computation
- Template rendering
- Cache management
These areas have more Python code and optimization opportunities.

## Session Resume - New Target Identified

**Date**: 2026-04-13 (continued)
**New Strategy**: Target actual Odoo Python code instead of C-extension-heavy PIL operations

### Scan Results

Identified cache key computation in `odoo/tools/cache.py` as HIGH PRIORITY target:

**Benchmark Results** (.codeflash/benchmark_cache_keys.py):
- Key function lookups: 0.42-1.33 µs per call (fast, not a bottleneck)
- Key function creation: **1656 µs per call** (VERY SLOW - 16.5s for 10k creations)

**Profile Results** (.codeflash/profile_cache_keys.py):
- `eval()`: 13.8s (42% of runtime) - expensive dynamic compilation
- `inspect.signature()`: 12.0s (37% of runtime) - heavyweight introspection
- String operations: 5.0s (15% of runtime) - join/genexpr overhead

**Root Cause**: `ormcache.determine_key()` uses:
1. `inspect.signature()` to introspect method parameters (runs every time)
2. String building to create lambda code
3. `eval()` to compile the lambda from string

**Optimization Opportunities**:
1. Cache signature inspection results (avoid repeated introspection)
2. Replace eval() with closure or pre-compiled approach
3. Optimize string building or bypass it entirely

This is **pure Python algorithmic work** with clear optimization potential.

## Next Steps

Profile the cache module in a real workload context and implement optimizations to:
- Reduce `determine_key()` overhead
- Test impact on actual Odoo operations that create many cached methods
