On Saturday, April 5, 2014 7:41:47 PM UTC-7, Andrew Juell wrote:
Running sage locally under Ubuntu to avoid any esoteric problems in the cloud, I discovered another issue...while I could iterate over Posets(7) and (8) and count their linear extensions with little trouble, attempting to do so with the 9-element posets overnight resulted in python eating up 4+GB of memory before giving up and nearly freezing my machine outright.
It's a memory leak:
sage: import gc
sage: from collections import Counter
sage: P=Posets(6)
sage: gc.collect()
1
sage: pre={id(c) for c in gc.get_objects()}
sage: for p in P: dummy=p.linear_extensions()
sage: gc.collect()
0
sage: post=Counter(type(o) for o in gc.get_objects() if id(o) not in pre)
sage: sorted(post.iteritems(),key=lambda t: t[1])
[(_ctypes.PyCFuncPtrType, 1),
(_ast.Attribute, 1),
(frame, 1),
(sage.categories.category.JoinCategory_with_category, 1),
(StgDict, 1),
(sage.categories.facade_sets.FacadeSets_with_category, 1),
(sage.structure.coerce_maps.DefaultConvertMap_unique, 1),
(thread._local, 1),
(listiterator, 1),
(sage.categories.homset.Homset_with_category, 1),
(sage.misc.cachefunc.CachedFunction, 1),
(sage.misc.function_mangling.ArgumentFixer, 1),
(collections.defaultdict, 1),
(ctypes.CDLL, 1),
(_ast.Module, 1),
(_ast.Interactive, 1),
(decimal._Log10Memoize, 1),
(_ast.Compare, 1),
(sage.categories.posets.Posets_with_category, 1),
(_ast.Assign, 1),
(sage.misc.cachefunc.CachedMethod, 1),
(sage.categories.finite_posets.FinitePosets_with_category, 1),
(sage.misc.constant_function.ConstantFunction, 2),
(ctypes._FuncPtr, 2),
(operator.itemgetter, 3),
(decimal.Context, 3),
(_ast.Call, 3),
(instance, 3),
(frozenset, 4),
(abc.abstractproperty, 4),
(uuid.UUID, 4),
(_ast.Name, 4),
(sage.misc.classcall_metaclass.ClasscallMetaclass, 4),
(wrapper_descriptor, 5),
(classmethod, 5),
(abc.ABCMeta, 6),
(classobj, 6),
(sage.structure.dynamic_class.DynamicClasscallMetaclass, 6),
(decimal.Decimal, 6),
(sage.structure.dynamic_class.DynamicMetaclass, 8),
(member_descriptor, 9),
(method_descriptor, 9),
(_weakrefset.WeakSet, 18),
(cython_function_or_method, 20),
(property, 27),
(set, 38),
(getset_descriptor, 55),
(type, 67),
(cell, 112),
(staticmethod, 170),
(module, 181),
(sage.combinat.posets.hasse_diagram.HasseDiagram, 318),
(sage.combinat.posets.linear_extensions.LinearExtensionsOfPoset_with_category, 318),
(sage.graphs.linearextensions.LinearExtensions, 318),
(sage.misc.cachefunc.CachedMethodCaller, 318),
(sage.graphs.base.static_sparse_backend.StaticSparseBackend, 318),
(sage.combinat.posets.posets.FinitePoset_with_category, 318),
(sage.misc.cachefunc.CachedMethodCallerNoArgs, 321),
(builtin_function_or_method, 377),
(sage.structure.coerce_dict.TripleDictEraser, 637),
(sage.structure.coerce_dict.TripleDict, 637),
(instancemethod, 638),
(weakref.KeyedRef, 656),
(sage.structure.coerce_dict.MonoDictEraser, 1274),
(sage.structure.coerce_dict.MonoDict, 1274),
(function, 1687),
(weakref, 2054),
(dict, 2684),
(tuple, 3773),
(list, 5675)]
As you can see for n=6, all the 318 sage.graphs.linearextensions.LinearExtensions instances are still in memory. I suspect it's something like this:
- The "linear_extension" method is cached. This means that the poset will keep the linear_extension alive
- The unique_parent of linear_extension property means that its construction arguments (which includes the poset!) are used as a strong key in a weak_value_dictionary. This means that as long as the linear_extension is alive, the key will remain and hence the category of linear_extension will keep the poset alive.
Voila, a classic memory leak. "cached_methods" and "unique_parent" combined are basically designed to invite memory leaks like this. Don't use them together unless you're prepared to think very hard about basically all the objects that might have a glancing interaction with the objects you're implementing.