Memory leak when creating/deleting animations?

Hi all,

I’m new with LVGL so bear with me. I’m using LVGL 8.1 (git hash 77e76aab2153787142f98defca1e0df0392bf2ed).

While testing switching animations, I encountered a memory allocation failure. It looks like starting an animation allocates some memory that is not freed after deleting the animation.

The documentation does not mention anything else that needs to be done other than to call lv_anim_del(var, func);

This is what I’m seeing before and after creation/deletion of an animation. Is this a memory leak or am I missing something?

before create
used:   3552 ( 87 %), frag:   5 %, biggest free:    520

after create
used:   3804 ( 93 %), frag:   0 %, biggest free:    292

before delete
used:   3804 ( 93 %), frag:   0 %, biggest free:    292

after delete
used:   3588 ( 88 %), frag:  17 %, biggest free:    424

This is the code that produces the output above:


#define EXPLODE_ANIMATION_FRAME_COUNT    (14u)
static const lv_img_dsc_t *explode_animation_frames[EXPLODE_ANIMATION_FRAME_COUNT] = {
    &logo1, &logo2, &logo3,  &logo4,  &logo5,  &logo6, &logo7,
    &logo8, &logo9, &logo10, &logo11, &logo12, &logo13, &logo14,
};

static lv_anim_t m_animation;
static lv_obj_t *m_current_frame;

static void screen_create(void) {
    log_info("Creating screen");

    lv_mem_monitor_t mon;
    lv_mem_monitor(&mon);
    log_warning("before create");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);

    m_current_frame = lv_img_create(lv_scr_act());
    lv_img_set_src(m_current_frame, explode_animation_frames[0]);
    lv_obj_align(m_current_frame, LV_ALIGN_CENTER, 0, 0);

    // Animation stuff
    lv_anim_init(&m_animation);
    lv_anim_set_exec_cb(&m_animation, animation_executor);

    lv_anim_set_var(&m_animation, m_current_frame);
    lv_anim_set_time(&m_animation, 400);
    lv_anim_set_values(&m_animation, 0, EXPLODE_ANIMATION_FRAME_COUNT - 1);

    if (lv_anim_start(&m_animation) == NULL) {
        log_error("Failed to start animation");
    }

    lv_mem_monitor(&mon);
    log_warning("after create");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);
}

static void screen_update(void) {
    // Nothing to do
}

static void screen_delete(void) {
    log_info("Deleting screen");

    lv_mem_monitor_t mon;
    lv_mem_monitor(&mon);
    log_warning("before delete");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);

    if (lv_anim_del(m_current_frame, animation_executor)) {
        log_info("Screen deleted");
    }

    lv_obj_del(m_current_frame);

    lv_mem_monitor(&mon);
    log_warning("after delete");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);
}

static void animation_executor(void * obj, int32_t param) {
    log_trace("Animating screen");
    int32_t new_frame = LV_CLAMP(0, param, EXPLODE_ANIMATION_FRAME_COUNT - 1);
    lv_img_set_src(m_current_frame, explode_animation_frames[new_frame]);
}

Thank you for your help!

EDIT: Added the missing lv_obj_del(m_current_frame); call as mentioned in the comments.

I just noticed that I am creating an image and not deleting it.

After adding lv_obj_del(m_current_frame); to screen_delete() I now get:

before create
used:   3552 ( 87 %), frag:   5 %, biggest free:    520

after create
used:   3804 ( 93 %), frag:   0 %, biggest free:    292

before delete
used:   3804 ( 93 %), frag:   0 %, biggest free:    292

after delete
used:   3588 ( 88 %), frag:  17 %, biggest free:    424

I’m still 36 bytes short. Where could they be?

It seems the memory leak comes from the image, not the animation.

    lv_mem_monitor_t mon;
    lv_mem_monitor(&mon);
    log_warning("before img create");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);

    m_current_frame = lv_img_create(lv_scr_act());

    lv_mem_monitor(&mon);
    log_warning("after img create");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);

    lv_obj_del(m_current_frame);

    lv_mem_monitor(&mon);
    log_warning("after img delete");
    log_warning("used: %6d (%3d %%), frag: %3d %%, biggest free: %6d",
                (int)(mon.total_size - mon.free_size), mon.used_pct, mon.frag_pct,
                (int)mon.free_biggest_size);

That code outputs:

before img create
used:   3552 ( 87 %), frag:   5 %, biggest free:    520

after img create
used:   3672 ( 90 %), frag:   0 %, biggest free:    424

after img delete
used:   3588 ( 88 %), frag:  17 %, biggest free:    424

This is the output with LV_LOG_LEVEL set to LV_LOG_LEVEL_TRACE:

[Trace]	(3.100, +8)	 lv_mem_monitor: begin 	(in lv_mem.c line #239)
[Trace]	(3.100, +0)	 lv_mem_monitor: finished 	(in lv_mem.c line #253)

before img create
used:   3552 ( 87 %), frag:   5 %, biggest free:    520

[Info]	(3.100, +0)	 lv_img_create: begin 	(in lv_img.c line #59)
[Trace]	(3.101, +1)	 lv_obj_class_create_obj: Creating object with 0x6005a4a8 class on 0x20207adc parent 	(in lv_obj_class.c line #45)
[Trace]	(3.101, +0)	 lv_mem_alloc: allocating 60 bytes 	(in lv_mem.c line #125)
[Trace]	(3.101, +0)	 lv_mem_alloc: allocated at 0x20207bdc 	(in lv_mem.c line #151)
[Trace]	(3.101, +0)	 lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #81)
[Trace]	(3.101, +0)	 lv_mem_alloc: allocating 28 bytes 	(in lv_mem.c line #125)
[Trace]	(3.101, +0)	 lv_mem_alloc: allocated at 0x20207c1c 	(in lv_mem.c line #151)
[Trace]	(3.101, +0)	 lv_mem_alloc: allocating 4 bytes 	(in lv_mem.c line #125)
[Trace]	(3.101, +0)	 lv_mem_alloc: allocated at 0x20207b58 	(in lv_mem.c line #151)
[Trace]	(3.101, +0)	 lv_obj_constructor: begin 	(in lv_obj.c line #404)
[Trace]	(3.101, +0)	 lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	(3.101, +0)	 lv_img_constructor: begin 	(in lv_img.c line #385)
[Trace]	(3.102, +0)	 lv_mem_monitor: begin 	(in lv_mem.c line #239)
[Trace]	(3.102, +0)	 lv_mem_monitor: finished 	(in lv_mem.c line #253)

after img create
used:   3672 ( 90 %), frag:   0 %, biggest free:    424

[Trace]	(3.102, +0)	 lv_obj_del: begin (delete 0x20207bdc) 	(in lv_obj_tree.c line #47)
[Trace]	(3.102, +0)	 event_send_core: Sending event 44 to 0x20207adc with 0x20201804 param 	(in lv_event.c line #405)
[Trace]	(3.102, +0)	 event_send_core: Sending event 44 to 0x20207adc with 0x20201804 param 	(in lv_event.c line #405)
[Trace]	(3.103, +1)	 event_send_core: Sending event 33 to 0x20207bdc with 0 param 	(in lv_event.c line #405)
[Trace]	(3.103, +0)	 lv_mem_realloc: reallocating 0x20207b58 with 0 size 	(in lv_mem.c line #184)
[Trace]	(3.103, +0)	 lv_mem_realloc: using zero_mem 	(in lv_mem.c line #186)
[Trace]	(3.103, +0)	 lv_mem_free: freeing 0x20207b58 	(in lv_mem.c line #161)
[Trace]	(3.103, +0)	 lv_mem_free: freeing 0x20207bdc 	(in lv_mem.c line #161)
[Trace]	(3.103, +0)	 event_send_core: Sending event 44 to 0x20207adc with 0x2020186c param 	(in lv_event.c line #405)
[Trace]	(3.103, +0)	 event_send_core: Sending event 44 to 0x20207adc with 0x2020186c param 	(in lv_event.c line #405)
[Trace]	(3.103, +0)	 lv_mem_monitor: begin 	(in lv_mem.c line #239)
[Trace]	(3.103, +0)	 lv_mem_monitor: finished 	(in lv_mem.c line #253)

after img delete
used:   3588 ( 88 %), frag:  17 %, biggest free:    424

Hi,

Such a small change in the used memory can come from the increased fragmentation too.

What if you create/delete the image 50 times?

Hmm I guess you’re right. If I create/delete in a loop, memory does not seem leak anymore, but those 36 bytes never come back.

It’s because when you first allocate the image let’s there are 10 mallocs inside. When you free the image there will be 10 holes in the memory (which causes the fragmentation). When you allocate a new image, the holes are filled, and so on.

It’s rare that it’s visible so cleanly (i.e. on malloc/free all the holes gets reallocated exactly as they were)

Ok, makes sense. It’s weird that the memory monitor reports the fragmented memory as “used” though. If there’s a 36-byte hole somewhere in my heap, I’d expect the memory monitor to tell me that I have 36 bytes available.

It’s because there is a descriptor for each hole that tells the size of the hole. So higher fragmentation results in more used memory due to the many descriptors for the fragments.

1 Like

Oh alright, I understand. Thanks for the explanation :slight_smile:

1 Like

You are welcome! :slight_smile: