arcade icon indicating copy to clipboard operation
arcade copied to clipboard

Look into improving Arcade's startup time

Open einarf opened this issue 2 years ago • 11 comments

Arcade can be pretty slow at startup. Especially the first time you launch it. The OS will do some caching causing future startups faster. Investigate what is taking up all this time. We know font loading is one of the reasons.

einarf avatar Apr 03 '22 02:04 einarf

Do we need to preload these default fonts? The examples seem to be the main place they're used.

https://github.com/pythonarcade/arcade/blob/c5ac3504204098dc85ee93782cbe40ace4723ae2/arcade/init.py#L571-L583

Defaults in the API seem to use Arial or calibri: https://github.com/pythonarcade/arcade/blob/bf28ded0f25bcefad70e85ae1bd6a02900404748/arcade/gui/widgets/text.py#L64 https://github.com/pythonarcade/arcade/blob/2a7c1f1513e3439a05adca806f761a7cc0a50a3f/arcade/text.py#L176 https://github.com/pythonarcade/arcade/blob/2a7c1f1513e3439a05adca806f761a7cc0a50a3f/arcade/text.py#L638 https://github.com/pythonarcade/arcade/blob/2a7c1f1513e3439a05adca806f761a7cc0a50a3f/arcade/text.py#L553

Once I'm done with some current projects, I can look at replacing this preload list with a libre font that's metrically compatible with Arial as part of #1135.

pushfoo avatar Oct 16 '22 03:10 pushfoo

I would love a patch to remove this default font loading. It is both slow, surprising (I'm not using any text in my application, but pay the font loading cost), and incidentally causes issues with Bazel build integration because Bazel does not support any files with whitespace (so it ignore them from the wheel): https://github.com/bazelbuild/rules_python/issues/617

ClintLiddick avatar Feb 06 '23 03:02 ClintLiddick

So that means there are two issues:

  • Resource files containing spaces
  • Avoid triggering pyglet's system font loader

einarf avatar Feb 06 '23 03:02 einarf

@ClintLiddick Quick fix for whitespace in files names now in development at least : https://github.com/pythonarcade/arcade/commit/f012e7b220b66aec8a74ea99b1a904119b97cd5e

We can probably find some way to disable the font loading. The issue is not the font loading itself. That's pretty fast. The real issue is that pyglet will scan all system fonts when you access the pyglet.text module proxy. This is then cached in the OS so it will be more or less instant next time your run your game (within the cache time)

Still. It's definitely annoying when you don't use text.

einarf avatar Feb 06 '23 04:02 einarf

Recap of what I was told in the pyglet Discord today:

  • Win32 GDI (Win 7-ish and fallback?) font loading currently scans the system font directory
  • I suggested a pyglet config flag
  • Ben suggested deferring font loading by default to allow people who ship their own fonts to use them

That's an upstream change as I understand it, but one that seems like it should be simple if I can figure out where the font loading actually gets invoked.

pushfoo avatar Feb 24 '24 05:02 pushfoo

We did some testing the other day. The font scanning is gone on windows since we no longer use GDI.

  • Media/sound initialization is the most time consuming
  • The input/jotstick module can add another 0.2 - 0.4s for directinput

Startup times will rely on OS. I haven't tested for linux and mac.

This is the worst case situation for me after profiling over a few days

    355/1    0.002    0.000    4.193    4.193 <frozen importlib._bootstrap>:1349(_find_and_load)
      258    3.987    0.015    4.000    0.016 {built-in method time.sleep}
    355/1    0.001    0.000    3.883    3.883 <frozen importlib._bootstrap>:1304(_find_and_load_unlocked)
    346/1    0.002    0.000    3.883    3.883 <frozen importlib._bootstrap>:911(_load_unlocked)
    314/1    0.001    0.000    3.883    3.883 <frozen importlib._bootstrap_external>:988(exec_module)
    786/2    0.001    0.000    3.883    1.942 <frozen importlib._bootstrap>:480(_call_with_frames_removed)
    314/1    0.002    0.000    3.883    3.883 {built-in method builtins.exec}
      2/1    0.000    0.000    3.883    3.883 arcade\__init__.py:1(<module>)
        1    0.000    0.000    3.823    3.823 arcade\sound.py:1(<module>)
        1    0.000    0.000    3.822    3.822 pyglet\media\__init__.py:1(<module>)
    84/42    0.000    0.000    3.236    0.077 {built-in method builtins.__import__}
3958/3714    0.002    0.000    3.185    0.001 <frozen importlib._bootstrap>:1390(_handle_fromlist)
       35    0.002    0.000    3.049    0.087 ctypes\__init__.py:343(__init__)
        1    0.000    0.000    3.048    3.048 pyglet\media\codecs\__init__.py:40(add_default_codecs)
        1    0.000    0.000    3.039    3.039 pyglet\media\codecs\__init__.py:88(have_ffmpeg)
        1    0.000    0.000    3.039    3.039 pyglet\media\codecs\ffmpeg_lib\__init__.py:1(<module>)
        7    0.000    0.000    3.032    0.433 pyglet\lib.py:81(load_library)
       21    0.000    0.000    3.027    0.144 ctypes\__init__.py:459(LoadLibrary)
        1    0.000    0.000    1.964    1.964 pyglet\media\codecs\ffmpeg_lib\libavcodec.py:1(<module>)
        1    0.000    0.000    0.652    0.652 pyglet\media\codecs\ffmpeg_lib\libavformat.py:1(<module>)
        1    0.000    0.000    0.531    0.531 pyglet\media\codecs\ffmpeg_lib\libavutil.py:1(<module>)
        1    0.000    0.000    0.447    0.447 arcade\application.py:1(<module>)
        1    0.000    0.000    0.418    0.418 pyglet\media\codecs\ffmpeg_lib\libswscale.py:1(<module>

einarf avatar Feb 24 '24 13:02 einarf

Recap of what I was told in the pyglet Discord today:

  • Win32 GDI (Win 7-ish and fallback?) font loading currently scans the system font directory
  • I suggested a pyglet config flag
  • Ben suggested deferring font loading by default to allow people who ship their own fonts to use them

That's an upstream change as I understand it, but one that seems like it should be simple if I can figure out where the font loading actually gets invoked.

Clarification, Windows 7 does use DirectWrite as well in Pyglet, it just doesn't have all the features that 8.1+ does (colored font characters/emojis for example).

Right now when you use a tuple of font names, a call of have_font is called for each font name on creation. This triggers pyglet to check the loaded custom fonts if a font exists, if it doesn't, then it will enumerate all system fonts to check for the name. While the initial call can take a while, a result against the database is cached (changed since arcade 2.6 was released) at least. On my system it took 0.00449824333190918 for 1031 entries, which isn't too bad in my opinion. Even if I doubled my fonts, it would only be 0.008 seconds.

That being said I did investigate a bit, and there is a way to check if a font exists for GDI+ without enumerating all of the fonts. With that fix in, it took 0.00024770002346485853 to check if a font exists. I can push this out to pyglet so it can be in the next version. As long as the latest pyglet is still compatible with Arcade 2.6, or whoever is using GDI+ for legacy/performance reasons, it should be significantly faster. I would be curious to see the load time difference between the old way and new way.

caffeinepills avatar Feb 25 '24 23:02 caffeinepills

@caffeinepills We don't really worry about 2.6 at this points, but I'm sure that's a great addition for other pyglet users if people are using GID. I'm not sure how widespread that is.

Looking into media and input initialization will probably benefit more people? I don't know if there's any improvements that can be done here.

In arcade we should start by not importing sound/media/input on arcade import by default.

einarf avatar Feb 25 '24 23:02 einarf