ChatGPT解决这个技术问题 Extra ChatGPT

How can I explicitly free memory in Python?

I wrote a Python program that acts on a large input file to create a few million objects representing triangles. The algorithm is:

read an input file process the file and create a list of triangles, represented by their vertices output the vertices in the OFF format: a list of vertices followed by a list of triangles. The triangles are represented by indices into the list of vertices

The requirement of OFF that I print out the complete list of vertices before I print out the triangles means that I have to hold the list of triangles in memory before I write the output to file. In the meanwhile I'm getting memory errors because of the sizes of the lists.

What is the best way to tell Python that I no longer need some of the data, and it can be freed?

Why not print out the triangles to an intermediate file, and read them back in again when you need them?
This question could potentially be about two quite different things. Are those errors from the same Python process, in which case we care about freeing memory to the Python process's heap, or are they from different processes on the system, in which case we care about freeing memory to the OS?

H
Havenard

According to Python Official Documentation, you can explicitly invoke the Garbage Collector to release unreferenced memory with gc.collect(). Example:

import gc

gc.collect()

You should do that after marking what you want to discard using del:

del my_array
del my_object
gc.collect()

Things are garbage collected frequently anyway, except in some unusual cases, so I don't think that will help much.
In general, gc.collect() is to be avoided. The garbage collector knows how to do its job. That said, if the OP is in a situation where he is suddenly deallocating a lot of objects (like in the millions), gc.collect may prove useful.
Actually calling gc.collect() yourself at the end of a loop can help avoid fragmenting memory, which in turn helps keep performance up. I've seen this make a significant difference (~20% runtime IIRC)
I'm using python 3.6. Calling gc.collect() after loading a pandas dataframe from hdf5 (500k rows) reduced memory usage from 1.7GB to 500MB
I need to load and process several numpy arrays of 25GB in a system with 32GB memory. Using del my_array followed by gc.collect() after processing the array is the only way the memory is actually released and my process survives to load the next array.
t
tshepang

Unfortunately (depending on your version and release of Python) some types of objects use "free lists" which are a neat local optimization but may cause memory fragmentation, specifically by making more and more memory "earmarked" for only objects of a certain type and thereby unavailable to the "general fund".

The only really reliable way to ensure that a large but temporary use of memory DOES return all resources to the system when it's done, is to have that use happen in a subprocess, which does the memory-hungry work then terminates. Under such conditions, the operating system WILL do its job, and gladly recycle all the resources the subprocess may have gobbled up. Fortunately, the multiprocessing module makes this kind of operation (which used to be rather a pain) not too bad in modern versions of Python.

In your use case, it seems that the best way for the subprocesses to accumulate some results and yet ensure those results are available to the main process is to use semi-temporary files (by semi-temporary I mean, NOT the kind of files that automatically go away when closed, just ordinary files that you explicitly delete when you're all done with them).


I sure would like to see a trivial example of this.
Seriously. What @AaronHall said.
@AaronHall Trivial example now available, using multiprocessing.Manager rather than files to implement shared state.
if I have a list of file pointers that are opened, do I 1) need to del the entire list or 2) each element in the list one at a time and then call gc.collect()?
@CharlieParker Let's say the list is x = [obj1, obj2, ...obj20]. To release the memory, any of the following measure can do (1) del x (2) x=[] (3) del x[:]. Just that for method (1), variable x is deleted and no longer accessible, thus the memory for the list x will also be released. While for methods (2) and (3), x is still accessible and still consumes memory.
l
legoscia

The del statement might be of use, but IIRC it isn't guaranteed to free the memory. The docs are here ... and a why it isn't released is here.

I have heard people on Linux and Unix-type systems forking a python process to do some work, getting results and then killing it.

This article has notes on the Python garbage collector, but I think lack of memory control is the downside to managed memory


Would IronPython and Jython be another option to avoid this problem?
@voyager: No, it wouldn't. And neither would any other language, really. The problem is that he reads in large amounts of data into a list, and the data is too large for the memory.
It would likely be worse under IronPython or Jython. In those environments, you're not even guaranteed the memory will be released if nothing else is holding a reference.
@voyager, yes, because the Java virtual machine looks globally for memory to free. To the JVM, Jython is nothing special. On the other hand, the JVM has its own share of drawbacks, for instance that you must declare in advance how large heap it can use.
It's rather awful implementation of Python garbage collector. Visual Basic 6 and VBA also have managed memory, but no one ever complained about memory not being freed there.
N
Ned Batchelder

Python is garbage-collected, so if you reduce the size of your list, it will reclaim memory. You can also use the "del" statement to get rid of a variable completely:

biglist = [blah,blah,blah]
#...
del biglist

This is and isn't true. While decreasing the size of the list allows the memory to be reclaimed, there is no guarantee when this will happen.
No, but usually it will help. However, as I understand the question here, the problem is that he has to have so many objects that he runs out of memory before processing them all, if he reads them into a list. Deleting the list before he is done processing is unlikely to be a useful solution. ;)
Also note that del doesn't guarantee that an object will be deleted. If there are other references to the object, it won't be freed.
will biglist = [ ] release memory?
yes, if the old list isn't referenced by anything else.
E
Eric O Lebigot

(del can be your friend, as it marks objects as being deletable when there no other references to them. Now, often the CPython interpreter keeps this memory for later use, so your operating system might not see the "freed" memory.)

Maybe you would not run into any memory problem in the first place by using a more compact structure for your data. Thus, lists of numbers are much less memory-efficient than the format used by the standard array module or the third-party numpy module. You would save memory by putting your vertices in a NumPy 3xN array and your triangles in an N-element array.


Eh? CPython's garbage collection is refcounting-based; it's not a periodic mark-and-sweep (as for many common JVM implementations), but instead immediately deletes something the moment its reference count hits zero. Only cycles (where refcounts would be zero but aren't because of loops in the reference tree) require periodic maintenance. del doesn't do anything that just reassigning a different value to all names referencing an object wouldn't.
I see where you're coming from: I will update the answer accordingly. I understand that the CPython interpreter actually works in some intermediate way: del frees the memory from from Python's point of view, but generally not from the C runtime library's or OS' point of view. References: stackoverflow.com/a/32167625/4297, effbot.org/pyfaq/….
Agreed as to the content of your links, but assuming the OP is talking about an error they get from the same Python process, the distinction between freeing memory to the process-local heap and to the OS doesn't seem likely to be relevant (as freeing to the heap makes that space available for new allocations within that Python process). And for that, del is equally effective with exits-from-scope, reassignments, etc.
L
Lucas

You can't explicitly free memory. What you need to do is to make sure you don't keep references to objects. They will then be garbage collected, freeing the memory.

In your case, when you need large lists, you typically need to reorganize the code, typically using generators/iterators instead. That way you don't need to have the large lists in memory at all.


If this approach is feasible, then it's probably worth doing. But it should be noted that you can't do random access on iterators, which may cause problems.
That's true, and if that is necessary, then accessing large data datasets randomly is likely to require some sort of database.
You can easily use an iterator to extract a random subset of another iterator.
True, but then you would have to iterate through everything to get the subset, which will be very slow.
R
Retzod

I had a similar problem in reading a graph from a file. The processing included the computation of a 200 000x200 000 float matrix (one line at a time) that did not fit into memory. Trying to free the memory between computations using gc.collect() fixed the memory-related aspect of the problem but it resulted in performance issues: I don't know why but even though the amount of used memory remained constant, each new call to gc.collect() took some more time than the previous one. So quite quickly the garbage collecting took most of the computation time.

To fix both the memory and performance issues I switched to the use of a multithreading trick I read once somewhere (I'm sorry, I cannot find the related post anymore). Before I was reading each line of the file in a big for loop, processing it, and running gc.collect() every once and a while to free memory space. Now I call a function that reads and processes a chunk of the file in a new thread. Once the thread ends, the memory is automatically freed without the strange performance issue.

Practically it works like this:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

I wonder why you are using `//``s instead of # in Python for comments.
I got mixed up between languages. Thank you for the remark, I updated the syntax.
J
Jason Baker

Others have posted some ways that you might be able to "coax" the Python interpreter into freeing the memory (or otherwise avoid having memory problems). Chances are you should try their ideas out first. However, I feel it important to give you a direct answer to your question.

There isn't really any way to directly tell Python to free memory. The fact of that matter is that if you want that low a level of control, you're going to have to write an extension in C or C++.

That said, there are some tools to help with this:

cython

swig

boost python


gc.collect() and del gc.garbage[:] work just fine when I am using large amounts of memory
J
Joril

As other answers already say, Python can keep from releasing memory to the OS even if it's no longer in use by Python code (so gc.collect() doesn't free anything) especially in a long-running program. Anyway if you're on Linux you can try to release memory by invoking directly the libc function malloc_trim (man page). Something like:

import ctypes
libc = ctypes.CDLL("libc.so.6")
libc.malloc_trim(0)

how do I pass a reference to the object I want to delete to the library you suggest? I have the variable names to them do I do lib.malloc_trim(var)?
I'm afraid malloc_trim doesn't work that way (see man page). Moreover I think that libc doesn't know anything about Python variable names, so this approach is not suitable for working with variables
N
Nosredna

If you don't care about vertex reuse, you could have two output files--one for vertices and one for triangles. Then append the triangle file to the vertex file when you are done.


I figure I can keep only the vertices in memory and print the triangles out to a file, and then print out the vertices only at the end. However, the act of writing the triangles to a file is a huge performance drain. Is there any way to speed that up?