2

I was spending a lot of time trying to understand why loading time of PySimpleGUI with Tk on Mac OS X takes so much time.

To be more specific:

window.refresh()

takes 9 seconds for my (quite simple) full UI. I reduced it for the investigation, so now it takes 3 seconds.

It is happening about 1/7 of the times, while on the other times it is normally short. Same app on Windows does not show this problem.

The reduced UI is this:

enter image description here

The layout goes like this:

Top (2)
--- Row ------->
------- Column ()
--------|--- Btn "Camera" | Btn "Close"
--------|--- 
--- Row ------->
------- Column (left-col)
--------| Row ------->
--------|--- Btn "4" | Btn "10" | Btn "20" | Btn "40" | 
--------|---- Column ()
--------|-----|--- CB "Scale Bar"
--------|-----|--- CB "White Bar"
--------| Row ------->
--------|--- Btn "BrFd" | Btn "DAPI" | Btn "FITC" | Btn "CY3"
--------| Row ------->
--------|--- Multiline | Btn ""
--------| Row ------->
--------|--- Multiline | Btn ""
--------| Row ------->
--------|--- Btn "" | Btn "Show Last" | Btn "Save" | Btn "Export IJ"
--------| Row ------->
--------|--- Btn "Export TIF" | Btn "Delete Checked"

I was running it with a profiler:

rm prof && python -m cProfile -o prof uscope.py

then used this code:

import pstats
from pstats import SortKey
p = pstats.Stats('prof')
p.sort_stats(SortKey.CUMULATIVE).print_stats(10)

to get this analysis:

Thu Apr 29 15:10:39 2021    prof

         204224 function calls (199897 primitive calls) in 4.099 seconds

   Ordered by: cumulative time
   List reduced from 2141 to 10 due to restriction 

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    552/1    0.003    0.000    4.100    4.100 {built-in method builtins.exec}
        1    0.000    0.000    4.100    4.100 uscope.py:24()
        1    0.000    0.000    3.460    3.460 /Users/u/Prog/c/pbio-printer/camera2/VerySimpleGUI2.py:335(build_and_run)
  393/389    3.170    0.008    3.170    0.008 {method 'call' of '_tkinter.tkapp' objects}
        1    0.000    0.000    2.978    2.978 /Users/u/opt/miniconda3/envs/camera/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py:8247(refresh)
        1    0.000    0.000    2.978    2.978 /Users/u/opt/miniconda3/envs/camera/lib/python3.7/tkinter/__init__.py:1175(update)
   278/10    0.002    0.000    0.638    0.064 :978(_find_and_load)
   277/10    0.001    0.000    0.638    0.064 :948(_find_and_load_unlocked)
   386/11    0.000    0.000    0.636    0.058 :211(_call_with_frames_removed)
    258/8    0.001    0.000    0.635    0.079 :663(_load_unlocked)

the problematic line seem to be this one:

 393/389    3.170    0.008    3.170    0.008 {method 'call' of '_tkinter.tkapp' objects}

I'd be very thankful for any clue regarding this problem!

ishahak
  • 6,585
  • 5
  • 38
  • 56
  • see https://stackoverflow.com/questions/43703610/why-does-tkinter-window-update-get-slower-over-time-within-my-program – Tkinter Lover Apr 29 '21 at 13:11
  • Can you please show us the code of the function that take a long time? – TheLizzard Apr 29 '21 at 13:40
  • to @TheLizzard: that code does nothing but `window = sg.Window(...)`+`window.refresh()`. the second one is the one to take most of the time – ishahak Apr 29 '21 at 15:20
  • 1
    to @TkinterLover: I looked there and can't understand the relation. The problem there is endless addition of elements, while here I was building a static GUI – ishahak Apr 29 '21 at 15:24
  • @ishahak ok then please post the code inside `.refresh()`. I suspect you have a `while True` loop there or something similar – TheLizzard Apr 29 '21 at 15:45
  • 1
    @TheLizzard, it isn't my code, it's [PySimpleGUI](https://github.com/PySimpleGUI/PySimpleGUI/blob/master/PySimpleGUI.py), and anyway: why only Mac? why only 15% of the times? – ishahak May 01 '21 at 20:44
  • @ishahak Well if you are sure that it isn't your code, your only option is not to use `pysimplegui`. Also sometimes there are differences in how `tkinter` (and other modules) are programmed on different OSs. For example look at [this](https://stackoverflow.com/q/67133407/11106801) bug I encountered recently. It only effects some OSs. – TheLizzard May 01 '21 at 20:47
  • Precisely the same issue here! Only MacOS and not always... I've noticed that when I first show a very simple (2-element) 'welcome' window just to visualise that the program has started, and meanwhile define my main window as invisible in background, then when I click the welcome window (having _no_ defined events/bindings), the main window gets finalised and refreshed _much_ quicker (typically 2-4 seconds compared to (not always) 16-30+ seconds without any interaction). I don't understand why. How to make it start quicker without having to click the window... – Lenka Čížková May 05 '21 at 14:25
  • 1
    @TheLizzard I've tried to replace the PySimpleGUI calls `window.refresh()` by tkinter calls accessing directly the respective tkinter components of the window, `window.TKroot.update()`, but the result was the same - just this one function taking up to 9 seconds or even more now and then. So, it seems to me that it's not PySimpleGUI that causes the problems here. (And as @ishahak mentioned, why it would make a difference between MacOS and Win when PySimpleGUI is the same code on both?) – Lenka Čížková May 05 '21 at 14:34
  • @lenka_cizkova, such a relief to find out that I'm not alone here! – ishahak May 05 '21 at 16:06
  • @lenka_cizkova Why do you need to run `.refresh` anyways? `tkinter`'s main loop would do that for you. Also if `.update` takes a long time only on MacOS must mean that `tkinter` is waiting for an event or MacOS takes a lot of time setting up the widgets. Try removing widgets and running the code until you can no longer see the 9 sec delay. That will tell you which widget/group of widgets take a lot of time. Also the first time you call `.update`/`.mainloop` in `tkinter`, the widgets get drawn on the screen. – TheLizzard May 05 '21 at 16:15
  • @TheLizzard I've implemented a background image indirectly by having a semi-transparent main window plus background window consisting purely of the background image. To do so, I have to know the size of the main window. But directly after finalization, the main window quite often does _not_ show the full size yet and then I either have to wait a couple of seconds (not nice) or to refresh. Indeed, as I've already mentioned, tkinter on Mac seems to wait for events in some cases; idea supported by the fact that clicking the welcome window makes it to go on quicker. – Lenka Čížková May 06 '21 at 07:55
  • 1
    @ishahak Do you observe this issue with refresh only at the beginning (first time setting up your GUI), or all the time? I've noticed that after I've added that welcome window at the beginning, I was able to drop most of the refresh() calls on Mac (still needed in Win, but refresh is much quicker in Win, so no big problem) - like if tkinter would need some sort of start / initial event and then it reacts much quicker. Yesterday on testing (after code changes made as mentioned), I've started the app 40+ times in a row and each time it took under 3 seconds; so much better than before! – Lenka Čížková May 06 '21 at 08:08

1 Answers1

2

I do not have a good explanation for this, but thanks to the comment added by @lenka_cizkova, I can now offer a workaround:

By adding a splash screen, even a very short one (100ms), the main screen is loaded immediately:

# Splash screen to eliminate long loading time on Mac OSX
if os.name != 'nt':
    SPLASH_IMAGE_FILE = 'img/my-logo.png'
    DISPLAY_TIME_MILLISECONDS = 100
    sg.Window('',
              [[sg.Image(SPLASH_IMAGE_FILE)]], transparent_color=sg.theme_background_color(),
              no_titlebar=True, keep_on_top=True).read(timeout=DISPLAY_TIME_MILLISECONDS, close=True)

This code should be placed before building and showing the main window.

ishahak
  • 6,585
  • 5
  • 38
  • 56