Recently I came across strange memory usage while using copy.deepcopy.
I have the following code example:
import copy
import gc
import os
import psutil
from pympler.asizeof import asizeof
from humanize import filesize
class Foo(object):
__slots__ = ["name", "foos", "bars"]
def __init__(self, name):
self.name = name
self.foos = {}
self.bars = {}
def add_foo(self, foo):
self.foos[foo.name] = foo
def add_bar(self, bar):
self.bars[bar.name] = bar
def __getstate__(self):
return {k: getattr(self, k) for k in self.__slots__}
def __setstate__(self, state):
for k, v in state.items():
setattr(self, k, v)
class Bar(object):
__slots__ = ["name", "description"]
def __init__(self, name, description):
self.name = name
self.description = description
def __getstate__(self):
return {k: getattr(self, k) for k in self.__slots__}
def __setstate__(self, state):
for k, v in state.items():
setattr(self, k, v)
def get_ram():
return psutil.Process(os.getpid()).memory_info()[0]
def get_foo():
sub_foo = Foo("SubFoo1")
for i in range(5000):
sub_foo.add_bar(Bar("BarInSubFoo{}".format(i), "BarInSubFoo{}".format(i)))
foo = Foo("Foo")
foo.add_foo(sub_foo)
for i in range(5000):
foo.add_bar(Bar("BarInFoo{}".format(i), "BarInFoo{}".format(i)))
return foo
def main():
foo = get_foo()
foo_size = asizeof(foo)
gc.collect()
ram1 = get_ram()
foo_copy = copy.deepcopy(foo)
gc.collect()
ram2 = get_ram()
foo_copy_size = asizeof(foo_copy)
print("Original object size: {}, Ram before: {}\nCopied object size: {}, Ram after: {}\nDiff in ram: {}".format(
filesize.naturalsize(foo_size), filesize.naturalsize(ram1), filesize.naturalsize(foo_copy_size),
filesize.naturalsize(ram2), filesize.naturalsize(ram2-ram1)
))
if __name__ == "__main__":
main()
What I tried to do, is to test the amount of memory used by the program before and after the copy.deepcopy. For this purpose, I created two classes.
I expected my memory usage to rise after the call to deepcopy in an amount equal to the size of the original object.
Strangly I got these results:
Original object size: 2.1 MB, Ram before: 18.6 MB
Copied object size: 2.1 MB, Ram after: 24.7 MB
Diff in ram: 6.1 MB
As you can see the difference in memory usage is aprox. 300% the size of the copied object.
** These results has been obtained using Python 3.8.5 on Windows 10 64 bit
What I tried?
- Running this example of code using Python2.7, the results was even stranger (more than 500% of the copied object size):
Original object size: 2.3 MB, Ram before: 34.3 MB
Copied object size: 2.3 MB, Ram after: 46.2 MB
Diff in ram: 11.9 MB
- Running on Linux using both Python3.8 and Python2.7 got the same results (respectively).
- Returning list of tuples instead of dict in
__getstate__got better results but far from what I was expecting - Using lists instead of dicts in the
Fooobject also got better results but also far from what I was expecting. - Using
pickle.dumps&pickle.loadsin order to copy the object has produced the same results.
Any toughts?