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

Performance issue with update of Label.text #3162

Open
em-oaken opened this issue Feb 5, 2025 · 6 comments
Open

Performance issue with update of Label.text #3162

em-oaken opened this issue Feb 5, 2025 · 6 comments
Labels
awaiting details More details are needed before the issue can be triaged. bug A crash or error in behavior. windows The issue relates to Microsoft Windows support.

Comments

@em-oaken
Copy link

em-oaken commented Feb 5, 2025

Describe the bug

Updating a label takes between 5-6ms per label. This is a problem for an application that has many labels and willing to have refresh rate 25Hz.

Steps to reproduce

Execution of program with py -m my_module.

Code snipet with line_profiler:

Timer unit: 1e-07 s

Line # Hits Time Per Hit % Time Line Contents

46                                               @profile
47                                               def render(self):
48        85       2655.0     31.2      0.0          context = self.canvas.context
49        85     134875.0   1586.8      0.5          context.clear()
50                                           (...)
58        85    4838747.0  56926.4     19.1          self.top_bar_infos[0].text = f'{self.tau.loopno:04}  |  {self.tau.game_duration:.2f}s'
59        85    4524132.0  53225.1     17.8          self.top_bar_infos[1].text = f'Day {self.tau.vtime:%d %H:%M:%S}'
60        85    4483225.0  52743.8     17.7          self.top_bar_infos[2].text = f'{len(self.colony.population)} ants'
61        85    4750631.0  55889.8     18.7          self.top_bar_infos[4].text = f'{1/self.tau.rt_loop_duration.total_seconds():.0f} fps'

self.top_bar_infos defined with self.top_bar_infos = [toga.Label('...', style=Pack(flex=1)) for _ in range(4)], where self is a toga.Box

Testing by replacing to self.infolabel.text = 'abc' leads to the same performance hit.

Expected behavior

I expect update time <1ms per label.

Screenshots

No response

Environment

  • Operating System: Windows 11
  • Python version: 3.13.1
  • Software versions:
    • Briefcase: 0.3.20
    • Toga: 0.4.8

Logs

No response

Additional context

Many thanks for your help in advance

Em

@em-oaken em-oaken added the bug A crash or error in behavior. label Feb 5, 2025
@freakboy3742 freakboy3742 added the windows The issue relates to Microsoft Windows support. label Feb 5, 2025
@freakboy3742
Copy link
Member

Thanks for the report.

There's four possible sources of slowdown here:

  1. Windows/Winforms itself. If the actual underlying Winforms Label.Text assignment is slow... there's not much we can do about it.
  2. Python.net. If the process of marshalling from Python to the .Net runtime is slow, that's a problem - but it's not a problem that we can do much about. It might be fixable, but it's a performance issue for python.net to resolve.
  3. Toga's layered architecture. This is under our control... but to some extent, it's also "the price of doing business". Abstraction layers always come with a performance penalty; it's possible you might just be hitting the limits of Toga's abstraction layer.
  4. The Winforms event loop. There is a known issue with Toga's Winforms backend - see Winforms event loop integration has a performance impact #2613. We'd very much like to fix this, but it's not completely clear how.

Unfortunately, the profiling trace you've provided doesn't give enough detail to work out which of these is the culprit.

In terms of ways to narrow this down:

It's difficult to rule out (1) or (2) in isolation; however, writing a bare bones Python.net app that just updates a label will at least give you an indication of the maximum possible refresh rate.

You can rule out (3) by using `my_label._impl.native.Text = "new label" to change the label. That sidesteps Toga's abstraction layer entirely, and jumps directly to the native assignment; it's not ideal as a solution, but at least points out if that's where the performance lag is.

You can test (4) by modifying this line of code in the Winforms backend. If using Delay(1) significantly increases the refresh rate, then you've found the culprit.

Finally, it's also worth pointing out that your expectations here might be unreasonable. Toga isn't a game framework. If you're updating text 25 times a second... a human isn't actually reading that text. They can't. 1/25th of a second isn't enough time to read anything of significance. If you think of other examples where desktop GUIs display content, text will often have 1Hz or 2Hz refresh rates so that the content can be read, or a graphical representation (such as a progress bar) will be used.

@freakboy3742 freakboy3742 added the awaiting details More details are needed before the issue can be triaged. label Feb 5, 2025
@mhsmith
Copy link
Member

mhsmith commented Feb 6, 2025

These timings imply a maximum of 200 text updates per second across all labels. That does seem unreasonably low.

It would require deeper profiling to find the root cause, but it's also possible that this is caused by Toga running redundant layout calculations (#2938).

@freakboy3742
Copy link
Member

200 updates a second would also point pretty hard at the 5ms delay in the event loop (1/200th of a second is 5ms).

@em-oaken
Copy link
Author

em-oaken commented Feb 8, 2025

Thanks for your answers, I understand that toga isn't made for the usage I was willing to make out of it. Next on my list was the fact that I was facing problem with the event loop running at 25-30ms from exiting cycle to new cycle.
I raise these points because I was trying to build a rudimentary gaming interface with a 25Hz refresh rate, but I understand Toga isn't the right animal for that.

For this issue, my hope was that triggering a Label.text change involved an immediate rendering which I would have wanted to disable, and then proceed to rendering all Labels in one go.

I still think Toga is amazing and the project needs to be continued!

@HalfWhitt
Copy link
Contributor

For this issue, my hope was that triggering a Label.text change involved an immediate rendering which I would have wanted to disable, and then proceed to rendering all Labels in one go.

I'm not entirely sure how the rendering, specifically, is handled on the backend. As I understand it, Windows is the only backend that even supports updating the rendered display mid-loop. It might be possible for things like calls down to native property setters to be deferred to once per event loop, but this would presumably require yet another intermediary layer to cache and return such values when accessed. This is something that runs the risk of becoming desynced, needs to be tested, and might or might not actually make anything faster because of the increased overhead.

#2938 is a related concept; Toga's handling of layout calculations is currently synchronous and may recalculate layouts many times per event loop. (#3063 is a (currently not working) stab at reducing this to once per loop.)

@freakboy3742
Copy link
Member

As I understand it, Windows is the only backend that even supports updating the rendered display mid-loop. It might be possible for things like calls down to native property setters to be deferred to once per event loop, but this would presumably require yet another intermediary layer to cache and return such values when accessed.

Winforms provides a SuspendLayout API that could be used to batch updates programatically. We're already using it for some very specific cases (e.g., getting an image rendition canvas).

#2938 is a related concept; Toga's handling of layout calculations is currently synchronous and may recalculate layouts many times per event loop. (#3063 is a (currently not working) stab at reducing this to once per loop.)

Agreed this is related; and in the case of Winforms, that's almost exactly the sort of place where we'd need to put the SuspendLayout call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting details More details are needed before the issue can be triaged. bug A crash or error in behavior. windows The issue relates to Microsoft Windows support.
Projects
None yet
Development

No branches or pull requests

4 participants