Skip to content

perf: eliminate per-frame allocations across all paint-hot paths and event dispatchers#245

Open
gmmcosta15 wants to merge 6 commits into
devfrom
perf/button-paint-loop-fix
Open

perf: eliminate per-frame allocations across all paint-hot paths and event dispatchers#245
gmmcosta15 wants to merge 6 commits into
devfrom
perf/button-paint-loop-fix

Conversation

@gmmcosta15

@gmmcosta15 gmmcosta15 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Description

Select the type:

  • Feature
  • Bug fix
  • Code refactor
  • Documentation

Eliminates repeated per-frame CPU work in the application's paint-hot paths and event dispatchers, reducing idle CPU from ~73% GIL to ~2% GIL (measured via py-spy on Raspberry Pi 5 aarch64).

  • list_model.py - QPainterPath cache keyed by (w, h) + painter.translate(); pre-computed QColor objects; icon scaled() cache keyed by QSize; QFont/QFontMetrics cache by point size; _get_tinted() LRU for tinted QPixmap composites; removed LosslessImageRendering hint; handlePath.clear() + addEllipse() path reuse
  • display_button.py - round-rect QPainterPath cache by (w, h); _get_font(pt) helper caches QFont by point size; icon + secondary icon scaled() cache by QSize; QColor pre-computation for hover gradient and secondary text; removed LosslessImageRendering hint
  • toggleAnimatedButton.py - icon scaled() cache keyed by QSize; handlePath.clear() reuse instead of fresh QPainterPath each frame
  • blocks_button.py - background QPainterPath and icon scaled() cache keyed by QSize; removed duplicate CE_PushButtonLabel draw call and per-paint setStyle() call
  • mainWindow.py - 5 custom event types pre-computed as class-level constants; single etype = event.type() call per event() dispatch instead of one per branch
  • screensaver.py - _TOUCH_INTS: frozenset[int] stores raw .value ints; event.type().value in _TOUCH_INTS bypasses per-call SIP enum construction
  • loadWidget.py - show() replaced with setVisible() override; 16ms animation timer is paused when the widget is hidden and resumed when shown
  • files.py - installEventFilter narrowed to self.parent() with QApplication fallback, reducing global filter scope
  • filesPage.py - _find_file_insert_position uses enumerate(self._model.entries) instead of rowCount() + model.index(i) + model.data() on every comparison, bypassing the SIP bridge

Motivation

py-spy profiling on the target Raspberry Pi 5 (aarch64) identified paintEvent, eventFilter, and SIP bridge calls (rowCount, model.data) as the dominant GIL consumers during idle. All changes avoid repeated allocation (paths, fonts, colors, pixmaps) and SIP crossings on the hot path. No behavioral changes.

@gmmcosta15 gmmcosta15 added enhancement New feature or request. MajorUpdate An update that introduces major changes overall codeto the Refactor Enhancing code's readability, maintainability, and extensibility while addressing technical debt. labels Jun 25, 2026
@gmmcosta15 gmmcosta15 self-assigned this Jun 25, 2026
@gmmcosta15 gmmcosta15 requested a review from HugoCLSC June 25, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request. MajorUpdate An update that introduces major changes overall codeto the Refactor Enhancing code's readability, maintainability, and extensibility while addressing technical debt.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant