# Demonstrate there are methods of garbage collection in CPython # There is always reference counting. This removes references that garbage, unless # they are part of a cycle of references. # Python also maintains a generational garbage collector that runs periodically. # Programmers can manage the generational garbage collector (e.g. turn it off) # but not the reference counting garbage collector. # Below is an example of code with circular references (doubly-linked list) # If we use the previous links and turn off the generational garbage collector, then # python3 eats up memory like crazy. If we comment out the previous links, then the # reference counter keeps memory use under control. # The debugging and analysis statements from the GC were less helpful than the timing tests, so we # turned those off # We ran and timed the code # With or without circular references (i.e. making reference-counting nearly ineffective) # With or without the mark-and-sweep garbage collector on # with ref counting in a position to work well and M/S on # ran in about 3 minutes. memory usage stayed constant at about 1GB # with ref counting in a position to work well and M/S off # ran in about 3 minutes. memory usage stayed constant at about 1.4GB # without ref counting in a position to work well and M/S on # ran in about 5 minutes. memory usage went up and down, but slow trend up # max memory usage was about 14 GB and min memory usage (other than at the # very beginning) was 4 GB. # without ref counting in a position to work well and M/S off # program killed by OS (or maybe by Python?) import gc class LinkedList: def __init__( self ): self.head = None def addNode( self ): n = Node() n.next = self.head if self.head is not None: self.head.prev = n # circular reference here! self.head = n class Node: def __init__( self ): self.numbers = list( range(1000000) ) self.next = None self.prev = None def makeList(): l = LinkedList() for i in range(10): l.addNode() def main(): # for i in range(1000): for i in range(100): makeList() if __name__ == '__main__': # turn off the generational collector gc.disable() # do that crazy work print( "Before main" ) main() print( "After main" ) # if we haven't given up yet, run the generational collect # and see what it finds. gc.enable() gc.set_debug(gc.DEBUG_STATS) gc.collect() print( "\n" ) print( gc.get_stats() )