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
Unexpected memory consumption since release 8.3.0 #5797
Comments
Hi. Let me ask two questions
|
Hi @radarhere
|
Hi @radarhere I hope I found the root cause in our code. We use the lib pi3d, down there in the code I see, that an numpy array is created. Here is an example code that shows this behavior. from PIL import Image
import numpy as np
# endless loop
while(True) :
np.array(Image.open("/home/pi/Pictures/Unu-2819.jpg")) Using
|
@helgeerbe are you saying that the garbage collector in python can't cope with the creation of numpy arrays that use byte arrays generated by PIL.Image.open()? What happens if you create python variables to "hang" the info on. i.e. from PIL import Image
import numpy as np
while(True) :
im = Image.open("/home/pi/Pictures/Unu-2819.jpg")
np_im = np.array(im)
# then with
# im = None
# np_im = None
PS and what happens if you comment out the np_im = .. line? |
I can run some further tests. Watching top in real-time I can see that memory is freed. But at the end memory consumption is faster than freeing. That explains the sigtooth graph. |
When I try it here (on Raspberry Pi, it's fine on ubuntu x64 with lots of memory) I find the installed version of pillow is often running It's generally a bad idea to try to second guess the GC but maybe something is needed for small memory computers. |
while(True) :
Image.open("/home/pi/Pictures/Unu-2819.jpg") Works fine. No memory consumption. while(True) :
np_arr = np.array(Image.open("/home/pi/Pictures/Unu-2819.jpg")) Does not work. while(True) :
im = Image.open("/home/pi/Pictures/Unu-2819.jpg")
np_arr = np.array(im)
im = None
np_arr = None Does not work. pip3 uninstall Pillow
pip3 install Pillow==8.2.0 And everything is fine again |
Yes, I've tried all permutations and it always seems to hang onto the memory with v8.3.0 though it never actually runs out for me - probably not big enough image. Using the older |
Given what you're seeing, I suspect this is due to #5379, where we replaced our Image's Are you sure this isn't a problem better addressed by the NumPy team? |
Hit the same issue - poor 512MB Heroku dynos didn't know what hit them. "Memory quota vastly exceeded." *chuckle*
Python 3.10.0 |
Hi @rlevine. To confirm my theory, would you be able to install https://github.com/radarhere/Pillow/tree/numpy (or just open PIL/Image.py and make the radarhere@2779305 changes) and see if that fixes the problem? |
Chicken dinner. Memory for 36 passes through the function goes from 1488 MiB to 267 MiB, about 1.28GB difference. Thanks! Rick Pillow==8.4.0 as distributed
8.4.0 with radarhere/Pillow@2779305 applied
|
I'm investigating this. The fix really simple: class ArrayData:
def __init__(self, new):
__array_interface__ = new But I still don't understand the nature of cyclic references here. That's what I see: import numpy, gc
from PIL import Image
im = Image.new('RGB', (4, 4))
try:
gc.disable()
gc.collect()
gc.set_debug(gc.DEBUG_LEAK | gc.DEBUG_STATS)
numpy.array(im)
gc.collect()
for item in gc.garbage:
print('>>>', type(item), repr(item))
finally:
gc.set_debug(0)
gc.enable()
gc.garbage.clear()
gc.collect()
And from my point of view there couldn't be any circular refs. |
Oh, I get it finally. It turns that every class definition is a circular reference to itself: In [15]: class ArrayData:
...: pass
...: ArrayData.__mro__
Out[15]: (__main__.ArrayData, object) The smallest case which will grow infinitely: gc.disable()
while True:
class ArrayData:
pass So we just should move class Image:
class _ArrayData:
def __init__(self, new):
__array_interface__ = new
def __array__(self, dtype=None):
...
return np.array(self._ArrayData(new), dtype) |
By the way, if anyone is curious about the performance of Pillow → NumPy conversions, there is an article with a helper function that works significantly faster: https://uploadcare.com/blog/fast-import-of-pillow-images-to-numpy-opencv-arrays/ Unfortunately, this enhancement is barely implementable within Pillow codebase. |
Nope, this is totally garbage pressure. We should always be very careful with circular references when working with large objects. |
Works! Thanks! |
I appear to be having this same issue with PIL==9.1.0. I am observing the same sawtooth memory pattern, followed by 100% memory usage, when loading and unloading PIL images in a loop. Additionally I am passing PIL objects through helper functions, which may have something to do with it, I am not certain yet. The images are loaded using PIL, then converted to np arrays, then unloaded from memory using @hugovk @homm could you try to reproduce? Same exact scripts as above. |
The original fix to this issue involved making Would you mind testing https://github.com/radarhere/Pillow/tree/numpy and seeing if that solves your problem? |
@radarhere Just installed. Here is the package info. I will be testing this shortly. Previous:
Current:
|
@radarhere Seems to still have the leak - cpu mem gradually increases to 100%. Should I revert back to an older version of PIL? Maybe 8.2.0? |
8.2.0 is the version before changed from If there is any older version of Pillow that works for you, that would be useful debugging information to have. Would you be able to open a new issue, specify your operating system details, and simple code to demonstrate the problem? |
Just to let you know. I upgraded to |
@radarhere Interesting - I will do so. I am trying to pinpoint where memory is not being released; do you have any recommendations on best practices for doing so? Currently I am just logging cpu memory, but I think it would be helpful for us if I could pinpoint the PIL/np operation that is causing the issue.
What is strange is that I am observing the same strange zig-zag memory pattern, which made me think it must have been related to your issue. I guess I may be seeing the same symptoms but for a different bug. |
Try running it under valgrind, using the massif tool. It's slow, but it gives a really good profile of where and when memory is allocated. Alternately, the tracemalloc in py3 works pretty well to narrow it down to a line of python. |
@wiredfool @pcicales @helgeerbe |
@Rahul-Matta That doesn't sound anything like what's happening here. You're best off posting a complete bug report as a new issue. |
Okay, @wiredfool #6781 as new bug. |
What did you do?
I'm the owner of picframe. This is a picture frame viewer for raspi, controlled via mqtt and automatticly integrated as mqtt device in homeassistant.
In an endless loop this programs displays one image on the frame, opens the next one and blend the new image smoothly in. To extract the image data PIL is used. So at the end just 2 images are kept in memory.
What did you expect to happen?
A stable memory consumption over a period of time. This was true up and including release 8.2.0.
I'm logging in a influxdb the system load and found this behavior.
Memory consumption is as expected stable. When suddenly the sawtooth line appears. (High cpu load two most left yellow peaks are the nightly backups).
What have I done?
What actually happened?
Starting with release 8.3.0 memory consumption is strange. Picframe starts with 6% total memory. While running, PIL allocated permanently memory which is freed frequently (sawtooth line). But at the end memory consumption reaches 100% in total and picframe crashes.
What are your OS, Python and Pillow versions?
Description: Raspbian GNU/Linux 10 (buster)
Release: 10
Codename: buster
Linux picframe 5.10.63-v7+ 1459 SMP Wed Oct 6 16:41:10 BST 2021 armv7l GNU/Linux
issue in picframe
Issue for picframe is tracked here
The text was updated successfully, but these errors were encountered: