Performance issue with update of Label.text
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
Thanks for the report.
There's four possible sources of slowdown here:
- Windows/Winforms itself. If the actual underlying Winforms
Label.Textassignment is slow... there's not much we can do about it. - 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.
- 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.
- The Winforms event loop. There is a known issue with Toga's Winforms backend - see #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.
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).
200 updates a second would also point pretty hard at the 5ms delay in the event loop (1/200th of a second is 5ms).
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!
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.)
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.