4

I've been reading a few different guides and tutorials, as I am interested in learning some best practices of programming. I was reading the How to be a Programmer guide, and there is a section about memory management, from which I have taken this piece of text:

But even with garbage collection, you can fill up all memory with garbage. A classic mistake is to use a hash table as a cache and forget to remove the references in the hash table. Since the reference remains, the referent is non-collectable but useless. This is called a memory leak. You should look for and fix memory leaks early. If you have long running systems memory may never be exhausted in testing but will be exhausted by the user.

The creation of new objects is moderately expensive on any system. Memory allocated directly in the local variables of a subroutine, however, is usually cheap because the policy for freeing it can be very simple. You should avoid unnecessary object creation.

I know Python has automatic garbage collection, although I don't understand much of how it works. But I know that objects will remain in memory if there are still references to it.

My question is: when should I worry about freeing objects from memory in Python? Preferably with some practical code as an example. They give an example of a hash table above, but I'm not sure if I understand it correctly, and so I can't put into practice. My idea is to follow the recommendation above and avoid such situations early in the development process!

4

Garbage collection works by releasing memory for objects that are no longer referenced by your software. Putting objects in a cache by reference, and then failing to eventually release those references, causes a memory leak because the garbage collector cannot release the memory used by objects that are still being referenced somewhere.

You should worry about this if, and when, you start holding references to objects for too long. A cache is the most obvious way to do this.

Like some other languages, Python has the concept of a Weak Reference. Use a weak reference when you don't want that reference to prevent the object from being garbage collected.

  • I did a little more research into garbage collection, and from what I understood, an object without references is basically when there are no more variables pointing to it. So, from my understanding, I should always take care if any object is not going to be used anymore and erase all references, so they will not be held for too long? Or maybe I should worry only about the objects that are large? – Teresa e Junior Mar 31 '16 at 18:02
  • The cache example is actually a good one, since any object-oriented language worth its salt will clean up your local variables anyway when the currently-executing method goes out of scope. By definition, cache is going to contain long-lived things. – Robert Harvey Mar 31 '16 at 18:04
  • Is a "cache" in this context something like just a dictionary in Python? – Teresa e Junior Mar 31 '16 at 18:07
  • 1
    Could be. Any time you put something in a dictionary, that thing will stay in memory as long as the dictionary as a whole. So if you have a dictionary that is global, and you keep stuffing stuff into it without ever deleting it, then that is a memory leak. This is the most obvious way to do basic caching in python. – Steven Burnap Mar 31 '16 at 18:10
  • Ah, OK! If it's mostly global objects that I should worry about, then it is not that difficult! Thank you for your help! – Teresa e Junior Mar 31 '16 at 18:13
2

Let's say you're writing some image editing software in Python similar to Photoshop:

enter image description here

And you're collaborating with a team of people to develop. The main point of the software is to edit images, and sometimes those images can be very high in resolution (4000x4000 pixels with 32-bit channels, leading to ~256 megabytes per image/layer).

In that case you might ask where you need to store or reference images. And the most obvious place is probably the layer system which is where the user adds/deletes image layers. Then the collection of layers might be owned and referenced by a "document" (which is a collection of layers). On top of that you might have smart filters which might want to reference layers to which they're being applied, along with layer styles. You have an undo system which probably wants to store undo actions which reference the layers they affect. The list goes on and on but many things might come to reference images and reference the things that are referencing the images.

When Should Images Be Freed?

And here's a question: when should the images be freed? Oddly I sometimes find GC enthusiasts turning this into some complex technical or theoretical CS question. It's not a complex question; it's not a trick question. It's a simple user-end design question.

The memory for those images should be freed when the user tells us he/she longer needs those images around, when they delete layers, close entire Photoshop documents, etc (give or take, perhaps some background thread needs to finish a tiny bit of processing first before those images are freed, but the "freeing process" begins when the user tells us they no longer need the image(s) around). That's when the memory should be freed, especially in a software where the user's document might require gigabytes of memory when they're doing very high-res work where it could become a serious usability issue if the images just linger around in memory without being freed until the entire software is closed.

Resource Management

But you can probably imagine if your colleagues get sloppy and start storing lifetime-extending image references all over the place without thinking, and lifetime-extending references to those things that reference images, and so forth, that you might very easily run into a scenario where a user requests to close a document and nothing gets freed because some thread or object that still persists is still referencing something that prevents all these things from being garbage collected whose lifetime is tied to the entire duration of the application.

You could find yourself looking at a mess like this as far as places storing lifetime-extending references to images (and this is not even nearly as gross as some examples I've encountered in the real world; this diagram is actually rather simple in comparison):

enter image description here

And that's a lot of places to forget to remove the appropriate references if we want to make sure that image is freed before the entire application is closed.

So GC is not a silver bullet against resource management of this sort for long-lived applications (not like one that does a computation and shuts down) where the lifetime of objects is tied to things the user is doing. In those cases you might not have to write low-level C code prone to errors like dangling pointers like:

free(memory);

But you might still need to remove object references from all the relevant containers (hash table is just one example) and background threads storing such object references and so forth in response to the user's input. It's still inevitably a manual affair because the user is the one telling us when the resources are no longer needed; it's not something that can be deduced automatically.

So one of the solutions to help prevent these types of leaks that Robert already mentioned are weak references, and I do wish more people paid attention to them for these types of applications when working with GC. They can really help a lot to prevent these types of mistakes and corresponding leaks which can be really difficult to trace down in hindsight.

Ah, OK! If it's mostly global objects that I should worry about, then it is not that difficult!

It's not just global objects. It's "persistent" objects. Those images in Photoshop might not be globally accessible. They might use dependency injection and limit scope and access by passing these images around to relevant places via parameter. Thread objects running in background might be given them via parameter and store them as member variables/references. Data structures being passed around via parameter might store references to them. But they are persistent state, as in their scope isn't tied to some local function which just computes something quickly and returns some output. In this type of software there's a notion of a persistent "application state" (ex: state local to somewhere close to the main entry point of the application), and it's not necessarily globally accessible, but it does linger around and persist for the duration of the application, and it's only going to free things (like images) under GC that it no longer references whatsoever in any part of its encapsulated members and the encapsulated members of those members and so forth along with auxiliary thread objects and what not.

So if you're writing software like this, with or without GC, you have to think clearly and carefully about resource management. That's a huge and unavoidable part of the technical design if you want to make sure it doesn't just continue to use more and more and more memory until the user shuts the whole thing down.

My question is: when should I worry about freeing objects from memory in Python?

For these types of applications, it's the same whether you're using Python or Java or C or C++ or anything else. The strategy to resource management to me begins with asking, "Who owns what?" It might be easy to think everything should "share ownership" of images that needs to reference them, but that's getting seducted by the temptations of GC. From a user-end standpoint only layers should own images. Only the document should own layers. Things of this sort. You might make one exception for the application history since it needs to make sure the necessary data is kept around to be capable of being undone depending on how you implement it (I'd find shared ownership reasonable there, but there should be a separate history per document, and not one for the entire application).

If you use weak references everywhere else that doesn't require ownership, that's already a great start. You might acquire strong references for short-lived durations in threads to ensure the objects don't get garbage-collected until the thread finishes processing it, but that's for short-lived durations inside some local function. Be very careful with where you store the persistent lifetime-extending references, because every time you do that, unless you're careful to null out/remove references at appropriate times (ex: in response to the proper user input events), that could translate to a logical leak.

Similar to C where every kind of memory deallocation is completely manual (even short-lived memory, not persistent application state), it's a nice habit to write your code to free the memory as soon or even before you even write the code to allocate it. Similarly if you're implementing like that hash table to cache objects in your example, I think it'd be a smart idea to write the code to remove the object reference from the hash table at the appropriate times if you can't use weak references, perhaps even before writing the code to insert the object to it, and test it and make sure that removal part works because it could easily fly under the radar of testing like a stealth fighter bug with silent leaks unless you very specifically test for that case. With GC I'd double up my testing efforts here because while it's not susceptible to dangling pointers whatsoever, the types of leaks you can get in exchange if you don't remove these references at the appropriate times can be very difficult to detect and trace down otherwise (the mistake might not be as "fatal" to the application but more difficult to detect since it'll appear to work fine except it's not freeing memory at the relevant time).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.