Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closing windows is slow. #145

Open
typesupply opened this issue Jun 7, 2021 · 1 comment
Open

Closing windows is slow. #145

typesupply opened this issue Jun 7, 2021 · 1 comment

Comments

@typesupply
Copy link
Member

typesupply commented Jun 7, 2021

Closing windows is slow. Example:

import time
import vanilla

class Test:

    def __init__(self):
        count = 24
        self.w = vanilla.Window(
            (200, 10 + (30 * count)),
            "Test"
        )
        y = 10
        for i in range(count):
            button = vanilla.Button(
                (10, y, -10, 20),
                "w.close",
                callback=self.button1Callback
            )
            setattr(self.w, f"button{i}", button)
            y += 30
        self.w.open()

    def button1Callback(self, sender):
        s = time.time()
        self.w.close()
        t = time.time() - s
        print("w.close", t)

try:
    import mojo
    Test()
except:
    from vanilla.test.testTools import executeVanillaTest
    executeVanillaTest(Test)

This is based on the "Align and Distribute" window in my Pop Up Tools extension, so this is a real world example. This takes about 0.7 seconds to close on my old iMac. This makes the UI feel very laggy.

I've tracked this down to the use of hasattr in the ultimate _breakCycles function. This is called once for every single view in a window, thus the more views a window has, the slower it becomes. I'm trying to find a way to speed this up but the best I'm coming up with are super hacky things like:

Edit: This code isn't correct. I'm sketching some new options.

# def _breakCycles(view):
#     """
#     Break cyclic references by deleting _target attributes.
#     """
#     if "setVanillaWrapper_" in view.__class__.__dict__:
#         try:
#             view._breakCycles()
#         except AttributeError:
#             pass
#     for view in view.subviews():
#         _breakCycles(view)

This reduces the _breakCycles time by about 40%, so it's still not great. It seems that a lot of time is wasted when the try: fails. I wonder if we could leverage getNSSubclass to give us a faster way to determine if _breakCycles needs to be called. Maybe we'd keep a set of class names that have gone through getNSSubclass that has a _breakCycles?

cc @justvanrossum @typemytype

@typesupply
Copy link
Member Author

Search view.__class__.__dict__ for a method unique to vanilla:

def _breakCycles(view):
    if "setVanillaWrapper_" in view.__class__.__dict__:
        obj = view.vanillaWrapper()
        try:
            obj._breakCycles()
        except AttributeError:
            pass
    for view in view.subviews():
        _breakCycles(view)

Use respondsToSelector:

def _breakCycles(view):
    if view.respondsToSelector_("vanillaWrapper"):
        obj = view.vanillaWrapper()
        obj._breakCycles()
    for view in view.subviews():
        _breakCycles(view)

FWIW, the respondsToSelector method gives me very fast performance.

typesupply added a commit that referenced this issue Dec 20, 2023
This addresses #145.

Closing the test window in the issue goes from taking 0.11 seconds to 0.01 seconds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant