Pympler
- Monitor Memory Usage By Python Objects¶Python has different sets of libraries for performing memory profiling like memory_profiler, guppy/heapy, scalene, etc. All these libraries provide usage of memory by python code in different ways. But there is no provision of monitoring memory usage of object created using user-defined classes in any of them. There are situations where we need to monitor memory usage by a particular kind of object and a Python library named pympler
can be very useful for those kinds of requirements. The pympler has a list of modules that lets us monitor memory usage by python code in various ways. As a part of this tutorial, we'll be explaining various modules available in pympler with examples.
Below is a list of modules that we'll discuss as a part of this tutorial.
classtracker
module in different ways.We'll start by importing pympler.
import pympler
asizeof
¶As a part of this section, we'll explore various methods available with asizeof
module to find out memory usage by various objects.
Below we have created two lists where the first is an actual list with all elements and the second is the python generator. We have then measured the size of both of them using the asizeof()
method.
from pympler import asizeof
l1 = [i for i in range(10)]
l2 = range(10)
print("Size of List l1 : %d bytes"%asizeof.asizeof(l1))
print("Size of List l1 : %d bytes"%asizeof.asizeof(l2))
print("Size of List l1,l2 Combined : %d bytes"%asizeof.asizeof(l1,l2))
print("Size of List l1 & l2 : %d bytes, %d bytes"%asizeof.asizesof(l1, l2))
pympler.asizeof.Asized
which has information about the memory usage of objects passed.Below we have explained the usage of both methods with simple examples.
asized = asizeof.asized(l1)
print("Object Type : ", type(asized))
print(asized)
print("Flat Size : %d, Total Size : %d\n"%(asized.flat, asized.size))
asized_objs = asizeof.asized(l1,l2)
print(asized_objs[0].format())
print(asized_objs[1].format())
print("\nObject Stats : ")
print(asizeof.asized(l1, detail=0, stats=1).format())
print("\nObject Details : ")
print(asizeof.asized(l1, detail=1).format())
We have explained how we can use the above methods.
print("Size of Single Item in a List : %d bytes"%asizeof.itemsize(l1))
print("Basic Size of Object : %d bytes"%asizeof.basicsize(l1))
print("Flat Size of Object : %d bytes"%asizeof.flatsize(l1))
print("List of Objects Referred by an Object : ", asizeof.refs(l1))
We have explained the usage of the above methods with simple examples below.
print("Is Object Class : ",asizeof.isclass(l1))
print("Is Object Builtin : ",asizeof.isbuiltin(l1))
print("Is Object Code : ",asizeof.iscode(l1))
print("Is Object Frame : ",asizeof.isframe(l1))
print("Is Object Function : ",asizeof.isfunction(l1))
print("Is Object method : ",asizeof.ismethod(l1))
print("Is Object Module : ",asizeof.ismodule(l1))
print()
import random
print("Is Object Module : ",asizeof.ismodule(random))
print()
def method_test():
return random.randint(1,100)
print()
print("Is Object Function : ",asizeof.ismethod(method_test))
print("Is Object method : ",asizeof.isfunction(method_test))
class Testing:
def __init__(self):
pass
def test_print(self):
print("Testing Class")
testing = Testing()
print()
print("Is Object Function : ",asizeof.ismethod(testing.test_print))
print("Is Object method : ",asizeof.isfunction(testing.test_print))
print("Is Object Class : ",asizeof.isclass(Testing))
print()
print("Is Object Builtin : ",asizeof.isbuiltin(min))
print("Is Object Builtin : ",asizeof.isbuiltin(sum))
print("Is Object Builtin : ",asizeof.isbuiltin(max))
classtracker
& classtracker_stats
¶As a part of this section, we'll explain through various examples how we can trace memory usage by particular kind of user-defined objects and then format stats of monitoring in different ways using classtracker
and classtracker_stats
modules.
Our first example is a simple example that monitors memory usage by objects created using the class RandomNumbersGenerator
. We have created a simple class named RandomNumbersGenerator
which when instantiated with size generates a numpy array of that size with a random number in the range 1-100. We'll be monitoring all memory usage by all objects created using this class.
In order to trace memory usage by a particular class, we first need to create an instance of ClassTracker()
. We then need to class which we want to monitor by calling the track_class()
method of ClassTracker
by giving it class reference and name as input. We can then call create_snapshot()
on the ClassTracker
instance and it'll record memory usage by objects of classes monitored by it at that time. We can call create_snapshot()
as many times as we want and it'll record memory usage at all times. We can then call the print_summary()
method from the stats object of ClassTracker
and it'll print all memory snapshots taken for registered classes.
Below we have explained the usage of methods by creating 3 lists and then deleting one list. We have taken snapshots at the beginning, after lists creation, and after one list deletion to notice changes.
import numpy as np
class RandomNumbersGenerator:
def __init__(self, size):
self.random_ints = np.random.randint(1,100, size)
from pympler import classtracker
ct = classtracker.ClassTracker()
ct.track_class(RandomNumbersGenerator, name=RandomNumbersGenerator.__name__)
ct.create_snapshot(description="Start")
rand1 = RandomNumbersGenerator((1000,1000))
rand2 = RandomNumbersGenerator((1000,1000))
rand3 = RandomNumbersGenerator((1000,1000))
ct.create_snapshot(description="Intermediate")
#ct.stats.print_stats()
del rand3
ct.create_snapshot(description="End")
ct.stats.print_summary()
ct.close()
As a part of our second example, we'll be demonstrating how we can monitor more than one class using a class tracker and how to clear stats collected.
Below we have created another class named RandomDistribution
which generates a random uniform distribution for size passed to it. We have explained how we can monitor the usage of both RandomDistribution
and RandomNumbersGenerator
classes.
class RandomDistribution:
def __init__(self, size):
self.uniform_dist = np.random.uniform(size=size)
from pympler import classtracker
ct = classtracker.ClassTracker()
ct.track_class(RandomNumbersGenerator, name=RandomNumbersGenerator.__name__)
ct.track_class(RandomDistribution, name=RandomDistribution.__name__)
ct.create_snapshot(description="Start")
rand1 = RandomNumbersGenerator((1000,1000))
rand2 = RandomNumbersGenerator((1000,1000))
unif_dist1 = RandomDistribution((100,100))
unif_dist2 = RandomDistribution((100,100))
ct.create_snapshot(description="Intermediate")
#ct.stats.print_stats()
del rand2, unif_dist2
ct.create_snapshot(description="End")
ct.stats.print_summary()
ct.clear()
ct.stats.print_summary()
ct.close()
As a part of this example, we'll explain how we can inform the class tracker to monitor only a particular object of a class rather than all objects.
Below we have created two RandomNumbersGenerator
and two RandomDistribution
objects. We are then monitoring them using the above described methods.
from pympler import classtracker
rand1 = RandomNumbersGenerator((1000,1000))
rand2 = RandomNumbersGenerator((1000,1000))
unif_dist1 = RandomDistribution((100,100))
unif_dist2 = RandomDistribution((100,100))
ct = classtracker.ClassTracker()
ct.track_object(rand1)
ct.track_change(rand1)
ct.track_object(rand2)
ct.track_change(rand2)
ct.track_object(unif_dist1)
ct.track_change(unif_dist1)
ct.track_object(unif_dist2)
ct.track_change(unif_dist2)
ct.create_snapshot(description="Start")
rand1.random_ints = np.random.randint(1,100, (100,100))
ct.create_snapshot(description="Intermediate")
del rand2
ct.create_snapshot(description="End")
ct.stats.print_summary()
#ct.stats.print_stats(clsname="RandomDistribution")
ct.close()
As a part of this example, we'll explain how we can remove a class that we don't want to monitor as well as how we can dump monitoring stats to an output file that we can load later.
Below we have first registered two classes which we created earlier for monitoring, then created instances of classes, deleted a few instances, and then unregistered one class from monitoring. We have also taken a memory snapshot between steps. We have then checked whether it’s still monitoring a new instance of that class getting created. At last, we have stored monitoring stats to an output file.
from pympler import classtracker
ct = classtracker.ClassTracker()
ct.track_class(RandomNumbersGenerator, name=RandomNumbersGenerator.__name__)
ct.track_class(RandomDistribution, name=RandomDistribution.__name__)
ct.create_snapshot(description="Start")
rand1 = RandomNumbersGenerator((1000,1000))
rand2 = RandomNumbersGenerator((1000,1000))
unif_dist1 = RandomDistribution((100,100))
unif_dist2 = RandomDistribution((100,100))
ct.create_snapshot(description="Intermediate")
#ct.stats.print_stats()
del rand2, unif_dist2
ct.create_snapshot(description="End")
ct.stats.print_summary()
## Detaches "RandomNumbersGenerator".
## Already monitored instance will be monitored but future instance won't be monitored
## detach_all() and detach_all_classes() detaches all objects and classes.
ct.detach_class(RandomNumbersGenerator)
del rand1
rand3 = RandomNumbersGenerator((1000,1000))
ct.create_snapshot(description="Last")
ct.stats.sort_stats().print_summary()
print("Tracked Classes : ", ct.stats.tracked_classes)
ct.stats.dump_stats("pympler_stats.out")
ct.close()
The classtracker
module has a ConsoleStats
class which lets us load monitoring stats from a file. Below we have reloaded again monitoring stats store to a file in a previous step and printed stats again to verify.
ct = classtracker.ClassTracker()
console_stats = classtracker.ConsoleStats(ct, "pympler_stats.out")
console_stats.print_summary()
print("Tracked Classes : ",console_stats.tracked_classes)
As a part of this example, we have again explained the usage of dumping stats to a file and then reloading it.
from pympler import classtracker
ct = classtracker.ClassTracker()
ct.track_class(RandomNumbersGenerator, name=RandomNumbersGenerator.__name__)
console_stats = classtracker.ConsoleStats(ct)
ct.create_snapshot(description="Start")
rand1 = RandomNumbersGenerator((1000,1000))
rand2 = RandomNumbersGenerator((1000,1000))
rand3 = RandomNumbersGenerator((1000,1000))
ct.create_snapshot(description="Intermediate")
#ct.stats.print_stats()
del rand3
ct.create_snapshot(description="End")
console_stats.print_summary()
console_stats.dump_stats("pympler_stats.out")
print("Tracked Classes : ",console_stats.tracked_classes)
ct.close()
ct = classtracker.ClassTracker()
console_stats = classtracker.ConsoleStats(ct, "pympler_stats.out")
console_stats.print_summary()
print("Tracked Classes : ",console_stats.tracked_classes)
As a part of this example, we have explained how we can format monitoring statistics as an HTML file which has charts explaining memory usage of a class.
Below we have again recreated an example almost the same as one of our previous examples but this time introduced HTML stats as well.
from pympler import classtracker, classtracker_stats
ct = classtracker.ClassTracker()
ct.track_class(RandomNumbersGenerator, name=RandomNumbersGenerator.__name__)
ct.track_class(RandomDistribution, name=RandomDistribution.__name__)
html_stats = classtracker_stats.HtmlStats(ct)
ct.create_snapshot(description="Start")
rand1 = RandomNumbersGenerator((1000,1000))
rand2 = RandomNumbersGenerator((1000,1000))
unif_dist1 = RandomDistribution((100,100))
unif_dist2 = RandomDistribution((100,100))
ct.create_snapshot(description="Intermediate")
#ct.stats.print_stats()
del rand2, unif_dist2
ct.create_snapshot(description="End")
ct.stats.print_summary()
html_stats.dump_stats("pympler_html_stats.out")
print("Tracked Classes : ",html_stats.tracked_classes)
ct.close()
html_stats.create_html("html_stats.html")
Other Files
As a part of our seventh example, we are explaining how we can inform class tracker to take snapshots periodically rather than we manually taking snapshot each time.
Below we have explained the usage of the above methods with a simple example.
import time
ct = classtracker.ClassTracker()
ct.track_class(RandomNumbersGenerator, name=RandomNumbersGenerator.__name__)
ct.track_class(RandomDistribution, name=RandomDistribution.__name__)
periodic_thread = classtracker.PeriodicThread(ct, 0.1)
periodic_thread.start()
rand1 = RandomNumbersGenerator((1000,1000))
unif_dist1 = RandomDistribution((100,100))
ct.stats.print_summary()
periodic_thread.tracker.stop_periodic_snapshots()
rand2 = RandomNumbersGenerator((1000,1000))
unif_dist2 = RandomDistribution((100,100))
ct.stats.print_summary()
periodic_thread.tracker.start_periodic_snapshots()
ct.stats.print_summary()
ct.close()
tracker
¶The tracker
module lets us monitor overall memory usage over time. It can let us track the difference in memory usage between summaries.
from pympler import tracker
Below we have explained the usage of methods with a simple example.
summary_tracker = tracker.SummaryTracker()
summary_tracker.print_diff()
s0 = summary_tracker.create_summary()
l = [i*i for i in range(10000)] ## Creating Temporary List to Check its presence in difference
s1 = summary_tracker.create_summary()
summary_tracker.print_diff(summary1=s0, summary2=s1)
sorted(summary_tracker.diff(summary1=s0, summary2=s1), key=lambda x: x[2], reverse=True)
muppy
¶The muppy module lets the developer detect memory leaks. As a part of this section, we'll explain the usage of various methods of muppy module.
from pympler import muppy
objs = muppy.get_objects()
print("Number of Objects : ", len(objs))
Below we have only kept objects of type list.
objs_by_type = muppy.filter(objs, Type=list)
objs_by_type[:5]
l = [i*i for i in range(4999)]
all_objs_referred_by_l = muppy.get_referents(l)
print("Number of Objects Referred by List l : ", len(all_objs_referred_by_l))
muppy.print_summary()
l = [i*i for i in range(10000)]
print("Size of List l : %d bytes"%muppy.getsizeof(l))
garbagegraph
¶This module lets us monitor cyclic objects. It has a class named GarbageGraph
which takes an input list of objects and generates graphviz visualization showing the relation between them which can help us detect a cycle. The GarbageGraph
is an extension of ReferenceGraph
which will introduce in the refgraph
section.
Below we have tried to create cyclic objects and then created a garbage graph of it.
from pympler import garbagegraph
a = "A"
b = True
c = {a:b}
d= [1,2,3]
e = {2:3}
ref_graph = garbagegraph.GarbageGraph([a,b,c, d, e])
write_graph()
method as graphviz graph with .ps
extension.ref_graph.write_graph("ref_graph.out")
!cat ref_graph.out
ref_graph.render("ref_graph1.out")
refbrowser
¶The refbrowser
module lets us print tree-like illustration for object referrers. Below we have created a tree-like exploration of objects using ConsoleBrowser
and FileBrowser
classes of the refbrowser
module.
from pympler import refbrowser
a = "A"
b = "B"
c = [a, b]
browser = refbrowser.ConsoleBrowser(b)
browser.print_tree()
str-+-list--dict-+-dict
| +-module(__main__)
| +-dict
| +-dict
|
+-frozenset--dict-+-module(sre_parse)
| +-function (_class_escape)
| +-function (_escape)
| +-function (_uniq)
| +-function (_parse_sub)
| +-function (_parse)
| +-function (_parse_flag
file_browser = refbrowser.FileBrowser(b)
file_browser.print_tree("tree.out")
str-+-list--dict-+-dict
| +-module(__main__)
| +-dict
| +-dict
|
+-frozenset--dict-+-module(sre_parse)
| +-function (_class_escape)
| +-function (_escape)
| +-function (_uniq)
| +-function (_parse_sub)
| +-function (_parse)
| +-function (_parse_flag
refgraph
¶The refgraph
module creates a graphviz graph of references.
write_graph()
method as graphviz graph with .ps
extension.Below we have created reference graph of a few objects and then also created a graphviz directed graph of it.
from pympler import refgraph
a = "A"
b = True
c = {a:b}
d= [1,2,3]
e = {2:3}
ref_graph = refgraph.ReferenceGraph([a,b,c, d, e])
ref_graph.write_graph("ref_graph_graphviz")
!cat ref_graph.out
ref_graph.render("ref_graph_graphviz")
summary
¶The summary module lets us create a summary of the list of objects.
summarize()
method in a table format.summarize()
method.Below we have explained the usage of these methods with simple examples.
from pympler import summary
sum_by_type = summary.summarize(objs_by_type)
summary.print_(sum_by_type)
sum_by_l = summary.summarize(all_objs_referred_by_l)
summary.print_(sum_by_l)
sum_full = summary.summarize(objs)
summary.print_(sum_full)
diff = summary.get_diff(sum_by_type, sum_full)
summary.print_(diff)
This ends our small tutorial explaining various modules and methods available with pympler. Please feel free to let us know your views in the comments section.
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
When going through coding examples, it's quite common to have doubts and errors.
If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.
You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.
If you want to