Ren'Py Profiling renpy project

BolHeX

Newbie
Nov 30, 2019
49
129
Does anyone have a good way of profiling a renpy project?
I've tried using "config.profile" but it output all the info into my .log file which wasn't very useful.

I would like to be able to open the profile data in snakeviz or the like if at all possible.

Also attaching a debugger would be nice but I'm pretty that isn't going to be possible.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,294
15,153
Does anyone have a good way of profiling a renpy project?
Yes, using the Python and libraries.

Something like:
Python:
init python:
    import cProfile
    import pstats

    profiler = cProfile.Profile()
    profileFile = renpy.os.path.normpath( config.basedir, 'profile.txt' )

    def saveProfile():
        FH = open( profileFile, "a" )
        FH.write( "\t--- Profile at {}---\n".format( renpy.time.strftime( "%b %d %H:%M:%S" ) ) )
        # I recommend to also use /print_stats/ to filter the output.
        pstats.Stats(profiler, stream=FH ).sort_stats("cumtime")
        FH.close()
        profiler.disable()

label whatever:
    $ profiler.enable()
    [...]
    $ saveProfile()


But what is there to profile exactly ?

Either you see a slowdown, then you know where your code isn't optimized, or you see no slowdown, and your code is optimized enough.


There's only four ways to slowdown Ren'Py (from most to less common):
  • Python in screens

    "Screens are updated at the start of each interaction, and each time an interaction is restarted.
    [...]
    Screens must not cause side effects that are visible from outside the screen. Ren'Py will run a screen multiple times, as it deems necessary. It runs a screen as part of the image prediction process, before the screen is first shown. As a result, if running a screen has side effects, those side effects may occur at unpredictable times." [ ]

    The most important part being the end of the first line, because an interaction can be restarted by many things, including hovering a button. This mean that on regular use, it's not exceptional that a screen is updated five time each second, and therefore for the Python code embedded in it to be computed five time each second.

    There's many way to solve this issue. Either you compute as most values as you can before displaying the screen:
    Python:
    label showWhatever:
        renpy.dynamic( "a", "b", "c" )
        $ a = [whatever computation]
        $ b = [whatever computation]
        $ b = [whatever computation]
        call screen whatever( a, b, c )
        return
    
    label whatever:
        [...]
        call showWhatever
        [...]
    Or you "cheat" and use the screen statement to call only once a function that will perform the Python computations:
    Python:
    init python:
        def initScreen():
            store.a = [whatever computation]
            store.b = [whatever computation]
            store.c = [whatever computation]
    
    screen myScreen():
        default useless = initScreen()
        [...]
    but this imply that all the variables used by the screen are global. Alternatively you can make those variables local to the screen by splitting the computations:
    Python:
    init python:
        def initScreenA():
            return [whatever computation]
        def initScreenB():
            return [whatever computation]
        def initScreenC():
            return [whatever computation]
    
    screen myScreen():
        default a = initScreenA()
        default b = initScreenB()
        default c = initScreenC()
        [...]
  • Too many buttons in a screen

    As I said above, hovering buttons is one of the reason for an interaction to restarts. Therefore, the more there's buttons that can be hovered, the more your embedded Python will impact Ren'Py speed.

    But this isn't the only reason why too many buttons can slow down Ren'Py. Each time the mouse move, Ren'Py will browse all the elements in the screen, asking them if it need to react to the new position of the mouse. For most elements, the answer is automatically "no", but for buttons they firstly need to see if the mouse is over them or not, what take time. So, the more there's buttons, the longer Ren'Py need to know if it should react to the new position of the mouse or not.
    This being said, it need more than 1200 buttons for this do starts to be sensible, while no normal screens need more than a dozen of them, three dozen at most if the screen is used for a mini-game.

  • Totally messed Python code

    Here it's obvious. The more messed is your own Python code, the longer it will need to be proceeded.
    But like Ren'Py pause between each code line, waiting for the player to notify that he'd read it and want to see what's coming next, this is usually only sensible when this Python code is used in a screen, either directly (see first point) or indirectly through a User Defined Displayable.

    It's really exceptional that this happen. Despite Python being one of the slowest script language, the code need to really be a big mess to slow down Ren'Py in a sensible way. Usually this kind of slowdown can only be noticed when you skip or when you auto advance at full speed.

  • Too many variables

    For it's Rollback feature to works, Ren'Py backup the variables at the starts of each interaction ; but not when it restarts. Therefore, the more there's variables, the slower it get.
    But once again this happening at the starts of the interaction, the rollback take place while the player is reading the dialog line. Therefore it need an astronomical number of variables for the slowdown to be sensible.
    As Reference, there's a game (that I'll not name) that currently have more than 23,000 variables. This more than significantly slowdown Ren'Py when you load a save file, but have no incidence when you play the game.



All this being said, if Ren'Py slowdown, it's not a profiler that will help.
Despite it being one of the slowest script language, that is due to it's structure, Python is still relatively well optimized. By example the difference between if VARIABLE (have the variable a none null value) and if VARIABLE is False (have the variable the boolean value "False") is atomic.
Because Ren'Py wait for a player input between each interaction, this mean that if some Python code is the source of a slowdown, it will be because the code is a real mess. And here a profiler can do nothing, because the issue isn't due to a lack of optimization, but purely structural ; it's not few lines that need to be changed, but the whole logic behind the code that have to be radically changed.



Also attaching a debugger would be nice but I'm pretty that isn't going to be possible.
*sigh*

There's no need for an attached debugger, because it natively exist.
The console permit you to track the current value of a variable in real time with its watch command. It also permit you to change the value of a variable, compute any Python code, and even execute any Ren'Py script statements.
Plus, of course, all .

After, if you want a bit more information, it suffice to search.
If what you want is to know what variables have changed, track this change, and all, there's a tool for that. If what you want is to track the called labels, and know in what label you are, there's a tool for that. And if you want to know precisely at what line of what file you currently are, there's this:
Python:
init python:

    def whereAmI():
        store.dFile, store.dLine = renpy.get_filename_line()

    config.interact_callbacks.append( whereAmI )

default dFile = None
default dLine = None
Just watch the two variables, or add an overlay screen showing them.

But, of course, if what you want is to debug the Python code, there isn't and will never be a debugger for that, because exec isn't debuggable. You need to debug the code as pure Python, therefore outside of Ren'Py.
But once again, if the code is so complex that a well placed print() (that will log in the console) can't be enough to debug it, the problem is either structural or logical.
 
  • Like
Reactions: BolHeX

BolHeX

Newbie
Nov 30, 2019
49
129
Thank you, It's less a slow down during the game play but a single long stutter/load during when initilising my various "manager" classes. With this I can see which is causing the long load times, since they seach and load for files in various directories I am 99% sure its this causing it.

As for the debugging, thanks for the linked tools. They could be useful. I knew about the console but being able to set breakpoint and what line by line execution would have been nice but not necessary.