Skip to content

[Bug]: Megabyte-level memory leak when using imshow() in a loop #30400

@borisnezlobin

Description

@borisnezlobin

Bug summary

When creating, plotting to, and closing a figure in a scope (I tested with this in a function called in a loop), the memory usage steadily increases, sometimes by several megabytes for even simple calls to imshow.

(p.s. in the code below, the generate_frame function gets called 3 times to "warm up" the Python interpreter, just in case. same results with or without those lines, though)

Code for reproduction

import gc
import numpy as np
import os
import psutil
import matplotlib
import matplotlib.pyplot as plt

matplotlib.use('Agg')

def generate_frame(frame_idx: int):
    plt.style.use("dark_background")
    fig, axes = plt.subplots(2, 3, figsize=(16, 8))
    axes = axes.flatten()

    for i in range(6):
        data = np.random.rand(1_000, 2_000)
        axes[i].imshow(data)

    plt.savefig("test.png", dpi=400, bbox_inches='tight', pad_inches=0.1)

    plt.clf()
    plt.close('all')
    del fig
    gc.collect()
    return None

def get_memory():
    return psutil.Process(os.getpid()).memory_info().rss / (1024**2)

for _ in range(3):
    generate_frame(-1)

prev = get_memory()
for i in range(0, 20):
    generate_frame(i)
    current = get_memory()
    print(f"Generated frame {i + 1}. Delta: {current - prev:.4f} MB of memory.")
    prev = current

Actual outcome

Generated frame 1. Delta: 4.1602 MB of memory.
Generated frame 2. Delta: 12.0195 MB of memory.
Generated frame 3. Delta: 0.2266 MB of memory.
Generated frame 4. Delta: 0.0859 MB of memory.
Generated frame 5. Delta: 4.1641 MB of memory.
Generated frame 6. Delta: 4.1758 MB of memory.
Generated frame 7. Delta: 0.0781 MB of memory.
Generated frame 8. Delta: 0.0430 MB of memory.
Generated frame 9. Delta: 0.0156 MB of memory.
Generated frame 10. Delta: 0.0117 MB of memory.
Generated frame 11. Delta: 0.1289 MB of memory.
Generated frame 12. Delta: 0.0117 MB of memory.
Generated frame 13. Delta: 0.3750 MB of memory.
Generated frame 14. Delta: 4.0859 MB of memory.
Generated frame 15. Delta: 4.1172 MB of memory.
Generated frame 16. Delta: 0.0234 MB of memory.
Generated frame 17. Delta: 0.0195 MB of memory.
Generated frame 18. Delta: 0.0195 MB of memory.
Generated frame 19. Delta: 0.0234 MB of memory.
Generated frame 20. Delta: 0.0234 MB of memory.

Expected outcome

Using the same code but with no reference to matplotlib, gives:

Generated frame 1. Delta: 4.0000 B of memory.
Generated frame 2. Delta: 12.0000 B of memory.
Generated frame 3. Delta: 176.0000 B of memory.
Generated frame 4. Delta: 504.0000 B of memory.
Generated frame 5. Delta: 164.0000 B of memory.
Generated frame 6. Delta: 0.0000 B of memory.
Generated frame 7. Delta: 160.0000 B of memory.
Generated frame 8. Delta: 0.0000 B of memory.
Generated frame 9. Delta: 28.0000 B of memory.
Generated frame 10. Delta: 16.0000 B of memory.
Generated frame 11. Delta: 0.0000 B of memory.
Generated frame 12. Delta: 24.0000 B of memory.
Generated frame 13. Delta: 24.0000 B of memory.
Generated frame 14. Delta: 0.0000 B of memory.
Generated frame 15. Delta: 0.0000 B of memory.
Generated frame 16. Delta: 0.0000 B of memory.
Generated frame 17. Delta: 0.0000 B of memory.
Generated frame 18. Delta: 0.0000 B of memory.
Generated frame 19. Delta: 0.0000 B of memory.
Generated frame 20. Delta: 0.0000 B of memory.

Code for that output:

import gc
import numpy as np
import os
import psutil

def generate_frame(frame_idx: int):
    for i in range(6):
        data = np.random.rand(100, 200)

    gc.collect()
    return None

def get_memory():
    return psutil.Process(os.getpid()).memory_info().rss / (1024)

for _ in range(3):
    generate_frame(-1)

prev = get_memory()
for i in range(0, 20):
    generate_frame(i)
    current = get_memory()
    print(f"Generated frame {i + 1}. Delta: {current - prev:.4f} B of memory.")
    prev = current

In other words, matplotlib should properly clean up references/resources to not generate a consistent upward trend in memory usage.

Additional information

In my real use-case, code very similar to this (but with colorbars. additional axes, etc.), using larger data, generates nearly 0.2 GB per frame as a consistent trend. I'm pretty sure the issue comes from matplotlib, as demonstrated with the example above.

Operating system

MacOS

Matplotlib Version

3.10.3

Matplotlib Backend

Agg

Python version

3.12.10

Jupyter version

N/A

Installation

pip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions