2.1 KiB
2.1 KiB
PyQtGraph Optimization Examples
Real-world optimization case studies from piker.
Case Study: Gap Annotations (1285 gaps)
Before: Individual pg.ArrowItem + SelectRect
Total creation time: 6.6 seconds
Per-item overhead: ~5ms
Memory: 1285 ArrowItem + 1285 SelectRect objects
Each gap was rendered as two separate QGraphicsItem instances (arrow + highlight rect), resulting in 2570 Qt objects.
After: Single GapAnnotations batch renderer
Total creation time:
104ms (server) + 376ms (client)
Effective per-item: ~0.08ms
Speedup: ~36x client, ~180x server
Memory: 1 GapAnnotations object
All 1285 gaps rendered via: - One PrimitiveArray for all rectangles - One QPainterPath for all arrows - Shared pen/brush across all items
Profiler Output (Client)
> 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
Profiler Output (Server)
> 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
Positioning/Update Pattern
For annotations that need repositioning when the view scrolls or zooms:
def reposition(self, array):
'''
Update positions based on new array data.
'''
# vectorized timestamp lookups (not linear!)
time_to_row = self._build_lookup(array)
# update rect array in-place
rect_memory = self._rectarray.ndarray()
for i, spec in enumerate(self._specs):
row = time_to_row.get(spec['time'])
if row:
rect_memory[i, 0] = row['index']
rect_memory[i, 1] = row['close']
# ... width, height
# trigger repaint (single call, not per-item)
self.update()Key insight: Update the underlying memory arrays directly, then call .update() once. Never create/destroy Qt objects during reposition.