Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Leak Issue with Custom Series #2104

Open
RDennis1 opened this issue May 30, 2023 · 2 comments
Open

Memory Leak Issue with Custom Series #2104

RDennis1 opened this issue May 30, 2023 · 2 comments
Labels
state: pending not addressed yet type: bug bug

Comments

@RDennis1
Copy link

Version of Dear PyGui

Version: 1.9.0
Operating System: Windows 10

My Issue/Question

While using a custom series in dearpygui, as explained in the official documentation, a memory leak has been observed.
Specifically, upon creation of a viewport with the custom series, the main process begins to steadily consume increasing amounts of memory.

Although the memory leak remains relatively small when following the demo application's example, it's been noticed that the leak becomes substantially larger when larger array arguments are passed to the x_data and y_data parameters. The memory leak seems to scale up proportionally with the size of the input data, which can potentially lead to significant performance issues and memory overflow when dealing with larger datasets.

To Reproduce

Steps to reproduce the behavior:

Copy and paste the following code into a python file and run it:

import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.create_viewport()
dpg.setup_dearpygui()

x_data = [0.0, 1.0, 2.0, 4.0, 5.0]
y_data = [0.0, 10.0, 20.0, 40.0, 50.0]

def callback(sender, app_data):

    _helper_data = app_data[0]
    transformed_x = app_data[1]
    transformed_y = app_data[2]
    #transformed_y1 = app_data[3] # for channel = 3
    #transformed_y2 = app_data[4] # for channel = 4
    #transformed_y3 = app_data[5] # for channel = 5
    mouse_x_plot_space = _helper_data["MouseX_PlotSpace"]   # not used in this example
    mouse_y_plot_space = _helper_data["MouseY_PlotSpace"]   # not used in this example
    mouse_x_pixel_space = _helper_data["MouseX_PixelSpace"]
    mouse_y_pixel_space = _helper_data["MouseY_PixelSpace"]
    dpg.delete_item(sender, children_only=True, slot=2)
    dpg.push_container_stack(sender)
    dpg.configure_item("demo_custom_series", tooltip=False)
    for i in range(0, len(transformed_x)):
        dpg.draw_text((transformed_x[i]+15, transformed_y[i]-15), str(i), size=20)
        dpg.draw_circle((transformed_x[i], transformed_y[i]), 15, fill=(50+i*5, 50+i*50, 0, 255))
        if mouse_x_pixel_space < transformed_x[i]+15 and mouse_x_pixel_space > transformed_x[i]-15 and mouse_y_pixel_space > transformed_y[i]-15 and mouse_y_pixel_space < transformed_y[i]+15:
            dpg.draw_circle((transformed_x[i], transformed_y[i]), 30)
            dpg.configure_item("demo_custom_series", tooltip=True)
            dpg.set_value("custom_series_text", "Current Point: " + str(i))
    dpg.pop_container_stack()

with dpg.window(label="Tutorial") as win:
    dpg.add_text("Hover an item for a custom tooltip!")
    with dpg.plot(label="Custom Series", height=400, width=-1):
        dpg.add_plot_legend()
        xaxis = dpg.add_plot_axis(dpg.mvXAxis)
        with dpg.plot_axis(dpg.mvYAxis):
            with dpg.custom_series(x_data, y_data, 2, label="Custom Series", callback=callback, tag="demo_custom_series"):
                dpg.add_text("Current Point: ", tag="custom_series_text")
            dpg.fit_axis_data(dpg.top_container_stack())

dpg.set_primary_window(win, True)
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

Open the task manager and observe the memory usage of the python process. It should steadily increase over time.

Expected behavior

The memory usage should remain constant after the initial creation of the custom series, and not increase over time.

Screenshots/Video

Example 1 (Demo application's example)

  1. Memory usage of the python process after the initial creation of the custom series:
MemoryLeak1
  1. Memory usage of the python process after ~1 minute:
MemoryLeak2

Example 2 (Larger input data)

Increasing the size of the input data to 100 elements for both x_data and y_data, the memory usage of the python process after the initial creation of the custom series is:

x_data = list(range(0, 100))
y_data = list(range(0, 1000, 10))
  1. Memory usage of the python process after the initial creation of the custom series:
MemoryLeak_Ex_2
  1. Memory usage of the python process after ~1 minute:
MemoryLeak_Ex_2_2

Standalone, minimal, complete and verifiable example

See above.

If you wish to test the memory leak with larger input data, you can use the following code:

Note: This leaks ~4MB a second.

import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.create_viewport()
dpg.setup_dearpygui()

x_data = list(range(0, 1000))
y_data = list(range(0, 10000, 10))


def callback(sender, app_data):

    _helper_data = app_data[0]
    transformed_x = app_data[1]
    transformed_y = app_data[2]
    # transformed_y1 = app_data[3] # for channel = 3
    # transformed_y2 = app_data[4] # for channel = 4
    # transformed_y3 = app_data[5] # for channel = 5
    mouse_x_plot_space = _helper_data["MouseX_PlotSpace"]  # not used in this example
    mouse_y_plot_space = _helper_data["MouseY_PlotSpace"]  # not used in this example
    mouse_x_pixel_space = _helper_data["MouseX_PixelSpace"]
    mouse_y_pixel_space = _helper_data["MouseY_PixelSpace"]
    dpg.delete_item(sender, children_only=True, slot=2)
    dpg.push_container_stack(sender)
    dpg.configure_item("demo_custom_series", tooltip=False)
    for i in range(0, len(transformed_x)):
        dpg.draw_text((transformed_x[i] + 15, transformed_y[i] - 15), str(i), size=20)
        dpg.draw_circle((transformed_x[i], transformed_y[i]), 15, fill=(50 + i * 5, 50 + i * 50, 0, 255))
        if (
            mouse_x_pixel_space < transformed_x[i] + 15
            and mouse_x_pixel_space > transformed_x[i] - 15
            and mouse_y_pixel_space > transformed_y[i] - 15
            and mouse_y_pixel_space < transformed_y[i] + 15
        ):
            dpg.draw_circle((transformed_x[i], transformed_y[i]), 30)
            dpg.configure_item("demo_custom_series", tooltip=True)
            dpg.set_value("custom_series_text", "Current Point: " + str(i))
    dpg.pop_container_stack()


with dpg.window(label="Tutorial") as win:
    dpg.add_text("Hover an item for a custom tooltip!")
    with dpg.plot(label="Custom Series", height=400, width=-1):
        dpg.add_plot_legend()
        xaxis = dpg.add_plot_axis(dpg.mvXAxis)
        with dpg.plot_axis(dpg.mvYAxis):
            with dpg.custom_series(
                x_data, y_data, 2, label="Custom Series", callback=callback, tag="demo_custom_series"
            ):
                dpg.add_text("Current Point: ", tag="custom_series_text")
            dpg.fit_axis_data(dpg.top_container_stack())

dpg.set_primary_window(win, True)
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

Additional context

I have only tested this on Windows 10 with Python 3.9.7 and DearPyGui 1.9.0.

I ran the above code using Pympler's SummaryTracker and it seems that thousands of float's are being created every second, which is likely the cause of the memory leak.

from pympler import tracker

tr = tracker.SummaryTracker()

# ... Code from above

tr.print_diff()
types # objects total size
float 2562004 58.64 MB
list 6810 20.06 MB
str 4242 297.61 KB
dict 1284 290.62 KB
tuple 1279 109.90 KB
int 2661 72.76 KB
@RDennis1 RDennis1 added state: pending not addressed yet type: bug bug labels May 30, 2023
@v-ein
Copy link
Contributor

v-ein commented May 30, 2023

Most probably it's leaking the callback arguments, where transformed_x and transformed_y represent those lots of floats. There's a known issue with reference counting in callbacks and handlers, see #2036 for more info. I hope to push a fix one day; unfortunately I'm too busy at the moment.

@Atlamillias
Copy link
Contributor

Atlamillias commented Jul 18, 2023

I recently came across this issue myself. Until the root cause(s) are fixed, I recommend using my approach to the problem on an as-needed basis using the Python C-API;

import ctypes


Py_DECREF = ctypes.pythonapi.Py_DecRef
Py_DECREF.argtypes = (ctypes.py_object,)
Py_DECREF.restype  = None

On my PC, your script leaked about twice as fast (roughly ~8MB/s). Calling the above Py_DECREF function on both transformed_x/y at the end of the callback stems the leak to ~1 MB every minute or so. However, calling it on app_data instead corks it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state: pending not addressed yet type: bug bug
Projects
None yet
Development

No branches or pull requests

3 participants