Load Sparkle without PyObjC's class scan (~150 MB less memory on macOS) - #2551
Open
ThomasWaldmann wants to merge 1 commit into
Open
Load Sparkle without PyObjC's class scan (~150 MB less memory on macOS)#2551ThomasWaldmann wants to merge 1 commit into
ThomasWaldmann wants to merge 1 commit into
Conversation
…mory
`objc.loadBundle('Sparkle', ...)` defaults to `scan_classes=True`, which wraps
every Objective-C class in the process (~67,000 on macOS 15) as a Python class
and keeps them in this module's globals for the lifetime of the app. Only the
frozen app bundle takes this path, which is why the shipped Vorta.app idles at
~220-250 MB while a venv install of the same code idles at ~80-120 MB.
Load only the framework and look up `SUUpdater` explicitly instead.
Measured on macOS 15.7 (M3) with a bundle built from this tree, main window
hidden, 35 s after launch: physical footprint 228.8 MB -> 78.6 MB, realized
ObjC classes 66,928 -> 2,322.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
@m3nu Have a look! Looks like Claude Fable 5.1 found a nice tweak to reduce memory usage significantly. Also, it found some other issues while doing that, see the other PRs. |
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
get_updater()loads the Sparkle framework withobjc.loadBundle('Sparkle', globals(), bundle_path). PyObjC's defaultscan_classes=Truedoes not just register Sparkle: it callsobjc_getClassList(), realizes every Objective-C class in the process (~67,000 on macOS 15 once Qt's Cocoa plugin and the system frameworks are loaded) and wraps each one as a Python class that is kept in this module's globals for the lifetime of the app. That costs ~150 MB of physical footprint and only happens in the frozen app bundle (the updater is only set up whensys.frozen), which is why the shippedVorta.appidles at ~220–250 MB while a venv install of the same code idles at ~80–120 MB.This PR loads only the framework (
scan_classes=False) and looks up the one class Vorta uses explicitly withobjc.lookUpClass('SUUpdater'). Nothing else changes:SUUpdaterwas the only bare name taken from the scan (hence the old# noqa: F821), other Sparkle classes remain reachable throughobjc.lookUpClass, and objects returned from Sparkle are wrapped lazily by PyObjC either way.Related Issue
Motivation and Context
Vorta is a tray-resident app, so its idle footprint is what users see all day. Measured on the shipped 0.11.5 bundle right after launch (scratch
$HOME, no backup run yet): 218 MB with the main window hidden, 250 MB with it shown;heapreported 66,870 realized ObjC classes and 71,919 Python objects (94 MB) allocated by the class scan. A pip install of the same version on the same machine: 91 MB / 120 MB. A 150-cycleborg createrun showed no per-backup growth, so the class scan is the dominant cost.How Has This Been Tested?
New unit test
tests/unit/test_updater.py(macOS only): assertsobjc.loadBundleis called withscan_classes=FalseandSUUpdateris obtained viaobjc.lookUpClass.pytest tests/unit: 265 passed, 7 skipped.ruff check/ruff format --checkclean.Two app bundles built with PyInstaller from this tree (macOS 15.7.9, Apple M3, Python 3.11, PyQt6 6.6.1), launched under a scratch
$HOMEwith the main window hidden and measured 35 s after launch:master(ebf7571)Isolated check with PyObjC 12 and the real
Sparkle.framework: the default scan costs +62 MB and adds 15,559 names to the module, of which 57 come from Sparkle;scan_classes=False+lookUpClasscosts +1 MB and returns the same class.Side note for maintainers: building from
masterneeded the spec'sdatasto collectassets/UIrecursively (the dialogs'.uifiles moved into subdirectories), otherwise the bundle fails at startup onassets/UI/dialogs/exception.ui. That is unrelated to this change and not included here.Types of changes
Checklist:
I provide my contribution under the terms of the license of this repository and I affirm the Developer Certificate of Origin.
🤖 Generated with Claude Code