Skip to content

Pyrsistent leaking memory on PMap discard #324

Description

@jonathanslenders

When adding and then removing many items from a PMap we are leaking memory somewhere.

Take the following script to reproduce:

import gc
from collections import Counter

from pyrsistent import pmap

# Create pmap, add 1M tems, then delete them.
m = pmap()
for i in range(1_000_000):
    m = m.set(i, i)
for i in range(1_000_000):
    m = m.discard(i)

# Now we have an empty map.
print(m)

# Print object counts.
gc.collect()
ct = Counter([type(o) for o in gc.get_objects()])
for k, v in sorted(ct.items(), key=lambda kv: kv[1])[-20:]:
    print([k, v])

This produces:

pmap({})
[<class 'types.GenericAlias'>, 76]
[<class 're._constants._NamedIntConstant'>, 76]
[<class 'staticmethod'>, 86]
[<class '_frozen_importlib.ModuleSpec'>, 90]
[<class 'module'>, 92]
[<class 'classmethod'>, 93]
[<class 'frozenset'>, 105]
[<class 'set'>, 117]
[<class 'cell'>, 214]
[<class 'member_descriptor'>, 378]
[<class 'type'>, 403]
[<class 'getset_descriptor'>, 433]
[<class 'builtin_function_or_method'>, 793]
[<class 'method_descriptor'>, 806]
[<class 'weakref.ReferenceType'>, 836]
[<class 'wrapper_descriptor'>, 1124]
[<class 'dict'>, 1258]
[<class 'tuple'>, 1316]
[<class 'function'>, 2313]
[<class 'list'>, 34080]

For comparison, after removing the set/discard lines from the Python snippet, we have the following that remains:

pmap({})
[<class 'types.GenericAlias'>, 76]
[<class 're._constants._NamedIntConstant'>, 76]
[<class 'staticmethod'>, 86]
[<class '_frozen_importlib.ModuleSpec'>, 90]
[<class 'module'>, 92]
[<class 'classmethod'>, 93]
[<class 'frozenset'>, 105]
[<class 'set'>, 117]
[<class 'cell'>, 214]
[<class 'list'>, 254]
[<class 'member_descriptor'>, 378]
[<class 'type'>, 403]
[<class 'getset_descriptor'>, 433]
[<class 'builtin_function_or_method'>, 794]
[<class 'method_descriptor'>, 806]
[<class 'weakref.ReferenceType'>, 838]
[<class 'wrapper_descriptor'>, 1124]
[<class 'dict'>, 1258]
[<class 'tuple'>, 1342]
[<class 'function'>, 2313]

Looks like len(m._buckets) is still 1048576 after discarding all entries.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions