172 lines
3.9 KiB
Markdown
172 lines
3.9 KiB
Markdown
|
|
---
|
||
|
|
name: piker-profiling
|
||
|
|
description: >
|
||
|
|
Piker's `Profiler` API for measuring performance
|
||
|
|
across distributed actor systems. Apply when
|
||
|
|
adding profiling, debugging perf regressions, or
|
||
|
|
optimizing hot paths in piker code.
|
||
|
|
user-invocable: false
|
||
|
|
---
|
||
|
|
|
||
|
|
# Piker Profiling Subsystem
|
||
|
|
|
||
|
|
Skill for using `piker.toolz.profile.Profiler` to
|
||
|
|
measure performance across distributed actor systems.
|
||
|
|
|
||
|
|
## Core Profiler API
|
||
|
|
|
||
|
|
### Basic Usage
|
||
|
|
|
||
|
|
```python
|
||
|
|
from piker.toolz.profile import (
|
||
|
|
Profiler,
|
||
|
|
pg_profile_enabled,
|
||
|
|
ms_slower_then,
|
||
|
|
)
|
||
|
|
|
||
|
|
profiler = Profiler(
|
||
|
|
msg='<description of profiled section>',
|
||
|
|
disabled=False, # IMPORTANT: enable explicitly!
|
||
|
|
ms_threshold=0.0, # show all timings
|
||
|
|
)
|
||
|
|
|
||
|
|
# do work
|
||
|
|
some_operation()
|
||
|
|
profiler('step 1 complete')
|
||
|
|
|
||
|
|
# more work
|
||
|
|
another_operation()
|
||
|
|
profiler('step 2 complete')
|
||
|
|
|
||
|
|
# prints on exit:
|
||
|
|
# > Entering <description of profiled section>
|
||
|
|
# step 1 complete: 12.34, tot:12.34
|
||
|
|
# step 2 complete: 56.78, tot:69.12
|
||
|
|
# < Exiting <description>, total: 69.12 ms
|
||
|
|
```
|
||
|
|
|
||
|
|
### Default Behavior Gotcha
|
||
|
|
|
||
|
|
**CRITICAL:** Profiler is disabled by default in
|
||
|
|
many contexts!
|
||
|
|
|
||
|
|
```python
|
||
|
|
# BAD: might not print anything!
|
||
|
|
profiler = Profiler(msg='my operation')
|
||
|
|
|
||
|
|
# GOOD: explicit enable
|
||
|
|
profiler = Profiler(
|
||
|
|
msg='my operation',
|
||
|
|
disabled=False, # force enable!
|
||
|
|
ms_threshold=0.0, # show all steps
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Profiler Output Format
|
||
|
|
|
||
|
|
```
|
||
|
|
> Entering <msg>
|
||
|
|
<label 1>: <delta_ms>, tot:<cumulative_ms>
|
||
|
|
<label 2>: <delta_ms>, tot:<cumulative_ms>
|
||
|
|
...
|
||
|
|
< Exiting <msg>, total time: <total_ms> ms
|
||
|
|
```
|
||
|
|
|
||
|
|
**Reading the output:**
|
||
|
|
- `delta_ms` = time since previous checkpoint
|
||
|
|
- `cumulative_ms` = time since profiler creation
|
||
|
|
- Final total = end-to-end time
|
||
|
|
|
||
|
|
## Profiling Distributed Systems
|
||
|
|
|
||
|
|
Piker runs across multiple processes (actors). Each
|
||
|
|
actor has its own log output.
|
||
|
|
|
||
|
|
### Common piker actors
|
||
|
|
- `pikerd` - main daemon process
|
||
|
|
- `brokerd` - broker connection actor
|
||
|
|
- `chart` - UI/graphics actor
|
||
|
|
- Client scripts - analysis/annotation clients
|
||
|
|
|
||
|
|
### Cross-Actor Profiling Strategy
|
||
|
|
|
||
|
|
1. Add `Profiler` on **both** client and server
|
||
|
|
2. Correlate timestamps from each actor's output
|
||
|
|
3. Calculate IPC overhead = total - (client + server
|
||
|
|
processing)
|
||
|
|
|
||
|
|
**Example correlation:**
|
||
|
|
|
||
|
|
Client console:
|
||
|
|
```
|
||
|
|
> Entering markup_gaps() for 1285 gaps
|
||
|
|
initial redraw: 0.20ms, tot:0.20
|
||
|
|
built annotation specs: 256.48ms, tot:256.68
|
||
|
|
batch IPC call complete: 119.26ms, tot:375.94
|
||
|
|
final redraw: 0.07ms, tot:376.02
|
||
|
|
< Exiting markup_gaps(), total: 376.04ms
|
||
|
|
```
|
||
|
|
|
||
|
|
Server console (chart actor):
|
||
|
|
```
|
||
|
|
> Entering Batch annotate 1285 gaps
|
||
|
|
`np.searchsorted()` complete!: 0.81ms, tot:0.81
|
||
|
|
`time_to_row` creation: 98.45ms, tot:99.28
|
||
|
|
created GapAnnotations item: 2.98ms, tot:102.26
|
||
|
|
< Exiting Batch annotate, total: 104.15ms
|
||
|
|
```
|
||
|
|
|
||
|
|
**Analysis:**
|
||
|
|
- Total client time: 376ms
|
||
|
|
- Server processing: 104ms
|
||
|
|
- IPC overhead + client spec building: 272ms
|
||
|
|
- Bottleneck: client-side spec building (256ms)
|
||
|
|
|
||
|
|
## Integration with PyQtGraph
|
||
|
|
|
||
|
|
Some piker modules integrate with `pyqtgraph`'s
|
||
|
|
profiling:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from piker.toolz.profile import (
|
||
|
|
Profiler,
|
||
|
|
pg_profile_enabled,
|
||
|
|
ms_slower_then,
|
||
|
|
)
|
||
|
|
|
||
|
|
profiler = Profiler(
|
||
|
|
msg='Curve.paint()',
|
||
|
|
disabled=not pg_profile_enabled(),
|
||
|
|
ms_threshold=ms_slower_then,
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Performance Expectations
|
||
|
|
|
||
|
|
**Typical timings:**
|
||
|
|
- IPC round-trip (local actors): 1-10ms
|
||
|
|
- NumPy binary search (10k array): <1ms
|
||
|
|
- Dict building (1k items, simple): 1-5ms
|
||
|
|
- Qt redraw trigger: 0.1-1ms
|
||
|
|
- Scene item removal (100s items): 10-50ms
|
||
|
|
|
||
|
|
**Red flags:**
|
||
|
|
- Linear array scan per item: 50-100ms+ for 1k
|
||
|
|
- Dict comprehension with struct array: 50-100ms
|
||
|
|
- Individual Qt item creation: 5ms per item
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- `piker/toolz/profile.py` - Profiler impl
|
||
|
|
- `piker/ui/_curve.py` - FlowGraphic paint profiling
|
||
|
|
- `piker/ui/_remote_ctl.py` - IPC handler profiling
|
||
|
|
- `piker/tsp/_annotate.py` - Client-side profiling
|
||
|
|
|
||
|
|
See [patterns.md](patterns.md) for detailed
|
||
|
|
profiling patterns and debugging techniques.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Last updated: 2026-01-31*
|
||
|
|
*Session: Batch gap annotation optimization*
|