Tk Source Code

View Ticket
Login
Ticket UUID: f642d7c0f495df33f7d2f2cd6d0df53cfca9cbe7
Title: MacOS slow performance of 8.6.11 vs 8.6.8
Type: Bug Version: 8.6.11
Submitter: E-Paine Created on: 2021-03-24 17:26:47
Subsystem: (unused) Assigned To: marc_culler
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2021-04-21 12:22:00
Resolution: Invalid Closed By: marc_culler
    Closed on: 2021-04-21 12:22:00
Description:
As-per https://bugs.python.org/issue43511, Tk performance on MacOS is
​much lower on Tk 8.6.11 than 8.6.8. Copying the results from there,
​times are milliseconds per iteration of a given script (which performs
​canvas actions).

+------------+----------+--------------+
| Tk Version | clearing | not clearing |
+============+==========+==============+
| 8.6.8      | 4.2      | 16.2         |
+------------+----------+--------------+
| 8.6.11     | 36.1     | 36.1         |
+------------+----------+--------------+

Tested on MacOS 11.2.3. I have not tried updates in between to find
​which version this behaviour first occurs in, these are just the two
​versions used by Python for easy comparison with the performance through
​tkinter.
User Comments: marc_culler (claiming to be Marc Culler) added on 2021-04-08 22:43:08:
The Tk update call is known to be expensive, is strongly advised against and
is an especially bad fit for macOS. It was originally meant for X11.  It
alternates between processing all events in the queue and calling XSync until
there are no more events in the queue.  There is no real XSync for Aqua.  The
macOS port attempts to simulate it by marking all windows as needing redisplay.

So I was curious how this benchmark would look if the calls to update were
removed, leaving just update idletasks, which should suffice to update the
canvas in a real application.  (Since the benchmark script keeps redrawing
the same square you can't really see if the canvas is being updated.)

Here are the times that I got with the current Tk 8.6 tip on an Intel mac
mini running Big Sur.

Using update:

$ tkbench % wish bench.tcl
Test clearing
16683.497893 microseconds per iteration
Test not clearing
17346.235111 microseconds per iteration

Not using update:
$ wish bench.tcl
Test clearing
52.856994 microseconds per iteration
Test not clearing
113.188684 microseconds per iteration

So the update slows things down by a couple of orders of magnitude while
almost surely making no difference to the graphics operations.

This seems to provide strong support for the standard advice that calls to
update should only be used as a last resort.  (By the way, for comparision
with the original ticket note that these times are in microseconds, not
milliseconds.)

I am not sure what meaningful information one can get from the fact that
programming practices which are discouraged and known to be problematic
are also slow, other than the obvious conclusion that the standard advice
should actually be taken seriously.

chrstphrchvz added on 2021-04-04 14:10:51:

I am going to defer to those more knowledgable on the topic, but to me

after idle [list goto b]
b: 

sounds more like what update idletasks does.

I'm not sure I understand how the reporter obtained results where non-clearing and clearing have nearly identical times for 8.6.11. I observe on my (likely much slower) system that not-clearing takes significantly longer (about 53ms per iteration) than clearing (18ms per iteration); successive iterations of not-clearing take longer and longer as more items are added to the canvas. I'm not aware whether the TerraLunar program is similarly adding a very large number of items to a canvas.


bll added on 2021-03-27 19:22:14:
Shouldn't the update command essentially be:

after idle [list goto b]
b: 

So why is it slower?

bll added on 2021-03-27 19:12:49:
My gui, updates the progress bar every .25 seconds, allows the user
to hit a stop button, so 'update' was required:

26823 milliseconds real time.

Recoding to use the method outlined on keep a gui alive, no 'update':

25456 milliseconds real time.

This is just a progress bar, not a bunch of stuff on a canvas.

I have to agree with Peter da Silva's comment.  Requiring people to code that
is a bit insane.  There should be an 'ewhile' command that does that.

chrstphrchvz added on 2021-03-26 16:25:13:

Clarification: the opener of this ticket authored the benchmark scripts; the opener of the Python tracker ticket authored the TerraLunar program.


chrstphrchvz added on 2021-03-26 15:24:10:

The reporter's bench.tcl and bench.py scripts essentially use a busy-wait loop to repeatedly call update; from briefly looking, the reporter's TerraLunar program appears to do this as well. Some in the Tcl/Tk community consider this an antipattern, and recommend other approaches for continuously redisplaying while remaining responsive to user input (see e.g. https://wiki.tcl-lang.org/page/Update+considered+harmful, https://wiki.tcl-lang.org/page/Keep+a+GUI+alive+during+a+long+calculation, https://tkdocs.com/tutorial/eventloop.html#nested). So I would guess that Tk probably cannot guarantee the performance of a program that calls update repeatedly, although update idletasks should be fine for forcing redrawing. I imagine this is something users easily run into, so maybe the official Tcl/Tk documentation or examples should mention it. I observe that the benchmark script spends 99% of its time in update, so I think that while this means that the performance issue might be avoided by using an alternative design, it also means an improved benchmark resembling something more useful is needed to tell if there's still a performance regression in Tk Aqua.


kevin_walzer added on 2021-03-25 18:30:45:
There have been significant changes in Tk's rendering code on macOS since 8.6.8, owing to changes in the underlying Mac API's, only some of which are documented. These changes have had to focus on simply keeping graphics display going without significant artifacts, and there have been some reports of graphics slowdown. There is ongoing reviewing review of the performance questions, but no simple or quick answers. Apple's API churn makes maintaining Tk on the Mac a much larger ongoing challenge that on X11 or Windows, alas.

E-Paine added on 2021-03-25 10:40:41:
Sorry, this is not an issue of M1 performance vs Intel performance. The reason this is discussed in the Python bug report is that Python Intel installers use Tk 8.6.8 while Universal2 installers use Tk 8.6.11. There was some initial discussion about whether the performance difference was because of the architecture, however, the results you can see in the initial post here are from an Intel machine.

nab added on 2021-03-24 19:39:12:
Hi Christopher,
you didn't quote the whole message:
The first test ("clearing") clears the canvas, creates a rectangle, then calls both `update` and `update_idletasks`.

maybe this is why timings are different.

also, from a strict AppleM1 point of view, my app running Tk8.7a4 is 20% faster on M1 that on Intel.
I've access to both M1 and Intel macBook, so I can run tests if you want.

++

chrstphrchvz added on 2021-03-24 19:15:00:

Subscribing myself for updates, although it sounds like this issue is specifically about performance on arm64, which I do not have access to.

Performance (of canvas and more generally) has been brought up on occasion by another Tk Aqua user, though I am not sure any concrete observations or improvements were made. I think it's possible that some focus on correctness has affected performance in recent releases. But I also currently think it could be unlikely for Tk Aqua to make a simple adjustment somewhere which leads to significant performance improvement.