How do you properly manage LVGL object memory in MP?

Just getting ready to ramp up screen building on a big LVGL MP project and trying to understand memory management (already have all basic LVGL running on device and in our simulator).

Right now if I do something naive like this to simulate a user changing screens:

    while 1:
        print('Available Mem: {}'.format(gc.mem_free()))
        scr = lv.obj()
        btn = lv.btn(scr)
        btn.align(lv.ALIGN.CENTER, 0, 0)
        label = lv.label(btn)
        label.set_text("Button {}".format(counter))
        lv.scr_load(scr)
        counter += 1
        await sleep_ms(1000)

the available memory drops a lot after each “frame” and quickly runs out of memory.

If I change lv.scr_load(scr) for the following line, the leak slows down, but does not go away completely

        lv.scr_load_anim(scr, lv.SCR_LOAD_ANIM.MOVE_RIGHT, 500, 0, True)

It seems that garbage collection is not enough to handle cleaning up after a screen (even with adding explicit gc.collect() calls).

What’s the right way to manage memory for screens and the objects they contain under LVGL MP?

Thanks!

1 Like

Hi @FoundationKen !

Your observation is correct.

When an LVGL screen is created (a screen is any LVGL object with no parent), it is added to a list of screens managed by the display.
Changing the active screen by calling lv.scr_load does not delete the previous screens.
Furthermore, even when the Python wrapper object goes out of scope and collected by gc, the LVGL screen still exists in that list.
You can see the number of screens currently managed by your display like this: lv.disp_get_default().screen_cnt

So to solve this, you need to make sure that when you no longer need a screen you delete it by calling the scr.delete() function.

For the purpose of measuring memory usage you also need to call gc.collect() to trigger Garbage Collection before calling gc.mem_free(), if you want to see the true size of available memory.

So here is your example, with added code to delete the screen on each iteration.
When running it - free memory remains constant between iterations.
You can also run this on the Online Simulator

counter = 0
scr = None

for i in range(0,10):    
    gc.collect()
    print('Available Mem: {}'.format(gc.mem_free()))

    if scr:
        scr.delete()
    scr = lv.obj()
    btn = lv.btn(scr)
    btn.align(lv.ALIGN.CENTER, 0, 0)
    label = lv.label(btn)
    label.set_text("Button {}".format(counter))
    lv.scr_load(scr)
    counter += 1

Thanks amirgon. That helped a lot! With my actual code I was also allocating and setting a keyboard input group like so:

    group = lv.group_create()
    group.add_obj(ta1)
    group.add_obj(ta2)
    common.keypad_indev.set_group(group)

I had to add code to keep track of the active group and call active_group._del() on each screen change, otherwise I was leaking 96 bytes each screen change.