Skip to content

Memory leak with python bindings on windows 11 #494

Description

@bricebrosig

Hi,

I'm using the python bindings for a pretty substantial desktop application. I've been using Dear Imgui with C / C++ for years now but only used the python bindings on this project for ~2 months (project is ~8 months old).

Version

Windows 11 25H2
python 3.12.1
imgui_bundle 1.92.801 (although leak is present in 1.6.2 and 1.92.800 as well)

Issue

Program leaks at ~ 6 MB / sec when idling. Our UI has a lot of windows / layout options that might be contributing to the high rate. I cannot share the code for our program but you can repro the issue with the following:

from imgui_bundle import imgui, hello_imgui

selected_idx = 0
items = ["Apple", "Banana", "Cherry"]

def gui():
    global selected_idx
    imgui.text("Choose a fruit:")
    _, selected_idx = imgui.list_box("##fruits", selected_idx, items)
    imgui.text(f"You selected: {items[selected_idx]}")
    imgui.show_demo_window()

hello_imgui.run(gui, window_title="Fruit Picker")

open up some of the demo window examples and then just sit and watch the memory usage go up in the process table
NOTE that this will not leak at ~6MB / sec - see logs below for the leak rate of the repro (I haven't tracked down what exactly accounts for the difference in leak rate. I suspect it is from lots of windows / callbacks registered with immapp - pure speculation)

I used tracemalloc to try and track down the leak but the issue is outside of the purview of things that tracemalloc will track. See logs below for the difference between tracemalloc and memory footprint captured by psutil (which reflects the value I see in the windows task manager).

Min Repro Logs
Here are logs of the memory usage every second while running the minimum repro (see below for the program). There is a time column, current and delta for the memory tracked by trace malloc, and current and delta tracked by psutil.
Observe that the python objects tracked do not increase in memory usage after initialization but the process in general climbs steadily.

  time (s)    tm_curr (KiB)   tm_delta (KiB)    ps_curr (KiB)   ps_delta (KiB)
         1         62.5928          62.5928      113824.0000      113824.0000
         2         62.7959           0.2031      116948.0000        3124.0000
         3         62.8428           0.0469      125508.0000        8560.0000
         4         62.8428           0.0000      126096.0000         588.0000
         5         62.8428           0.0000      126520.0000         424.0000
         6         62.8428           0.0000      126824.0000         304.0000
         7         62.8428           0.0000      127288.0000         464.0000
         8         62.8428           0.0000      127760.0000         472.0000
         9         62.8428           0.0000      128264.0000         504.0000
        10         62.8428           0.0000      128616.0000         352.0000
        11         62.8428           0.0000      129172.0000         556.0000
        12         62.8428           0.0000      129676.0000         504.0000
        13         62.8428           0.0000      130192.0000         516.0000
        14         62.8428           0.0000      130768.0000         576.0000
        15         62.8428           0.0000      131296.0000         528.0000
        16         62.8428           0.0000      131840.0000         544.0000
        17         62.8428           0.0000      132352.0000         512.0000
        18         62.8428           0.0000      132868.0000         516.0000
        19         62.8428           0.0000      132932.0000          64.0000
        20         62.8428           0.0000      133104.0000         172.0000
...
       124         62.8428           0.0000      140900.0000          40.0000
       125         62.8428           0.0000      140944.0000          44.0000
       126         62.8428           0.0000      141124.0000         180.0000
       127         62.8428           0.0000      141164.0000          40.0000
       129         62.8428           0.0000      141208.0000          44.0000
       130         62.8428           0.0000      141388.0000         180.0000
       131         62.8428           0.0000      141428.0000          40.0000
       132         62.8428           0.0000      141472.0000          44.0000
       133         62.8428           0.0000      141608.0000         136.0000
       134         62.8428           0.0000      141648.0000          40.0000
       136         62.8428           0.0000      141696.0000          48.0000
       137         62.8428           0.0000      141872.0000         176.0000
       138         62.8428           0.0000      141908.0000          36.0000
       139         62.8428           0.0000      141952.0000          44.0000
       140         62.8428           0.0000      142132.0000         180.0000
       141         62.8428           0.0000      142176.0000          44.0000
       142         62.8428           0.0000      142340.0000         164.0000
       144         62.8428           0.0000      142516.0000         176.0000

Production App Logs
Here are the same logs but on our production application - again, I cannot share any of that code but I'll note that we use immapp and we register 11 different windows each with their own gui function and 3 layout splits.

  time (s)    tm_curr (KiB)   tm_delta (KiB)    ps_curr (KiB)   ps_delta (KiB)
         1        509.2324         509.2324      270864.0000      270864.0000
         2        509.9023           0.6699      277064.0000        6200.0000
         3        509.9512           0.0488      283136.0000        6072.0000
         4        509.9512           0.0000      289320.0000        6184.0000
         5        509.9512           0.0000      295540.0000        6220.0000
         6        509.9512           0.0000      301636.0000        6096.0000
         7        509.9512           0.0000      307648.0000        6012.0000
         8        509.9512           0.0000      314124.0000        6476.0000
         9        509.9512           0.0000      320608.0000        6484.0000
        10        509.9512           0.0000      326896.0000        6288.0000
        11        509.9512           0.0000      333384.0000        6488.0000
        12        509.9512           0.0000      339828.0000        6444.0000
        13        509.9512           0.0000      346100.0000        6272.0000
        14        509.9512           0.0000      352320.0000        6220.0000
        15        509.9512           0.0000      358392.0000        6072.0000
        16        509.9512           0.0000      364648.0000        6256.0000
        17        509.9512           0.0000      371100.0000        6452.0000
        18        509.9512           0.0000      377040.0000        5940.0000
        19        509.9512           0.0000      383116.0000        6076.0000
        20        509.9512           0.0000      389336.0000        6220.0000
...
       110        509.9512           0.0000      947852.0000        6508.0000
       111        509.9512           0.0000      954584.0000        6732.0000
       112        509.9512           0.0000      961108.0000        6524.0000
       113        509.9512           0.0000      967680.0000        6572.0000
       114        509.9512           0.0000      974440.0000        6760.0000
       115        509.9512           0.0000      980936.0000        6496.0000
       116        509.9512           0.0000      987140.0000        6204.0000
       117        509.9512           0.0000      993380.0000        6240.0000
       118        509.9512           0.0000      999876.0000        6496.0000
       119        509.9512           0.0000     1006668.0000        6792.0000
       120        509.9512           0.0000     1013184.0000        6516.0000
       121        509.9512           0.0000     1019608.0000        6424.0000
       122        509.9512           0.0000     1026100.0000        6492.0000
       123        509.9512           0.0000     1032984.0000        6884.0000
       124        509.9512           0.0000     1039664.0000        6680.0000
       125        509.9512           0.0000     1046180.0000        6516.0000
       126        509.9512           0.0000     1052712.0000        6532.0000
       127        509.9512           0.0000     1058960.0000        6248.0000
       128        509.9512           0.0000     1065708.0000        6748.0000
       129        509.9512           0.0000     1072268.0000        6560.0000
       130        509.9512           0.0000     1078944.0000        6676.0000

You'll note that the leak is orders of magnitude larger on more significant apps. You'll note once again, that tracemalloc doesn't capture anything other than the startup objects - the leak is not inside of any python code.

NOTE also that a similar bug has surfaced for me using C/C++ dear imgui (not imgui bundle). There was a small leak present when using the GLFW / OpenGL3 backends. Our workaround at that time was to switch to DirectX11 (we only need to support Windows) as there was no leak in that backend. I see that python bindings are forced into glfw and opengl3 (right? I was unable to build python bindings from source that used windows directx11 backend...)

NOTE regarding tracemalloc and its ability to filter domains to capture objects allocated outside of python - I'm not sure if that feature is limited or bugged in my version of python but when I exclude the python memory address using domain filters in tracemalloc, there are no allocations traced (but the memory usage increases none-the-less).
Minimal reproducible example

Python program that reproduces and logs the leak using tracemalloc and psutil (NOTE if you don't have / don't want to pip install psutil, you can simply open up task manager or process table of your choice and observe the memory usage climbing over time while the program idles).

from imgui_bundle import imgui, hello_imgui
import tracemalloc
import psutil
import time

selected_idx = 0
items = ["Apple", "Banana", "Cherry"]


first_dump: bool = True
dump_timer: float = 0.0
g_tm_prev: float = 0.0
g_ps_prev: float = 0.0
def log():
  global dump_timer, g_tm_prev, g_ps_prev, first_dump
  if dump_timer > 1.0:
    if first_dump:
      first_dump = False
      print("%10s %16s %16s %16s %16s" % ('time (s)', 'tm_curr (KiB)', 'tm_delta (KiB)', 'ps_curr (KiB)', 'ps_delta (KiB)'))  
    
    time = imgui.get_time()
    tm_curr, _ = tracemalloc.get_traced_memory()
    tm_curr = tm_curr / (1024)
    ps_curr = psutil.Process().memory_info().rss / (1024)
    tm_delta = tm_curr - g_tm_prev
    ps_delta = ps_curr - g_ps_prev
    g_ps_prev = ps_curr
    g_tm_prev = tm_curr
    print("%10d %15.4f  %15.4f  %15.4f  %15.4f " % (time, tm_curr, tm_delta, ps_curr, ps_delta))
    dump_timer = 0.0
  else:
    dump_timer += imgui.get_io().delta_time

def gui():
    global selected_idx
    imgui.text("Choose a fruit:")
    _, selected_idx = imgui.list_box("##fruits", selected_idx, items)
    imgui.text(f"You selected: {items[selected_idx]}")
    imgui.show_demo_window()

    log()


tracemalloc.start()
hello_imgui.run(gui, window_title="Fruit Picker")
tracemalloc.stop()

after launching, open a few of the examples:
Image

note that you could omit the log() function and observe that the memory continues to leak with just the UI code from the demo. Just in case there was suspicion that tracemalloc or psutil were responsible for the leak - they are not.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions