# 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: ```python 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.