Skip to content

Commit 867a117

Browse files
committed
Item 59: Use tracemalloc to understand memory usage and leaks
1 parent 6860518 commit 867a117

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-0
lines changed

item_59_use_tracemalloc.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Item 59: Use tracemalloc to understand memory usage and leaks
2+
3+
4+
# Memory management in the default implementation of Python, CPython, uses
5+
# reference counting. This ensures that as soon as all references to an
6+
# object have expired, the referenced object is also cleared. CPython also
7+
# has a built-in cycle detector to ensure that self-referencing objects are
8+
# eventually garbage collected.
9+
10+
# In theory, this means that most Python programmers don't have to worry about
11+
# allocating or deallocating memory in their programs. It's taken care of
12+
# automatically by the language and the CPython runtime. However, in practice,
13+
# programs eventually do run out of memory due to held reference. Figuring out
14+
# where your Python programs are using or leaking memory proves to be a
15+
# challenge.
16+
17+
# The first way to debug memory usage is to ask the gc built-in module to list
18+
# every object currently known by the garbage collector. Although it's quite
19+
# a blunt tool, this approach does let you quickly get a sense of where your
20+
# program's memory is being used.
21+
22+
# Here, I run a program that wastes memory by keeping references. It prints
23+
# out how many objects were created during execution and a small sample of
24+
# allocated objects.
25+
26+
# item_59_use_tracemalloc_using_pc.py
27+
import item_59_use_tracemalloc_using_gc
28+
# 4944 objects before
29+
# 4955 objects after
30+
# {'_loaders': [('.cpython-35m-x86_64-linux-gnu.so', <class '_frozen_importlib_external.ExtensionFileL
31+
# set()
32+
# {'imageio', 'mujoco_py-0.5.7-py3.5.egg-info', 'pip', 'keras_tqdm', 'pyglet-1.2.4.dist-info', 'easy-i
33+
34+
# The problem with gc.get_objects is that it doesn't tell you anything about
35+
# how the objects were allocated. In complicated programs, a specific class
36+
# of object could be allocated many different ways. The overall number of
37+
# objects isn't nearly as important as identifying the code responsible for
38+
# allocating the objects that are leaking memory.
39+
40+
# Python 3.4 introduces a new tracemalloc built-in module for solving this
41+
# problem. tracemalloc makes it possible to connect an object back to where
42+
# it was allocated. Here, I print out the top three memory usage offenders in
43+
# a progam using tracemalloc:
44+
45+
# item_59_use_tracemalloc_top_n.py
46+
import item_59_use_tracemalloc_top_n
47+
# /home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_waste_memory.py:7: size=3539 KiB (+3539 KiB), count=100000 (+100000), average=36 B
48+
# /home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_top_n.py:6: size=1264 B (+1264 B), count=2 (+2), average=632 B
49+
# <frozen importlib._bootstrap_external>:476: size=485 B (+485 B), count=6 (+6), average=81 B
50+
51+
# It's immediately clear which objects are dominating my program's memory
52+
# usage and where in the source code they were allocated.
53+
54+
# The tracemalloc module can also print out the full stack trace of each
55+
# allocation (up to the number of frames passed to the start method). Here, I
56+
# print out the stack trace of the biggest source of memory usage in the
57+
# program:
58+
59+
# item_59_use_tracemalloc_with_trace.py
60+
import item_59_use_tracemalloc_with_trace
61+
# File "/home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_waste_memory.py", line 7
62+
# a.append(10 * 230 * i)
63+
# File "/home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_with_trace.py", line 6
64+
# x = waste_memory.run()
65+
66+
# A stack trace like this is most valuable for figuring out which particular
67+
# usage of a common function is responsible for memory consumption in a
68+
# program.
69+
70+
# Unfortunately, Python 2 doesn't provide the tracemalloc built-in module.
71+
# There are open source packages for tracking memory usage in Python 2 (such
72+
# as heapy), though they do not fully replicate the functionality of
73+
# tracemalloc.
74+
75+
76+
# Things to remember
77+
78+
# 1. It can be difficult to understand how Python programs use and leak
79+
# memory.
80+
# 2. The gc module can help you understand which objects exist, but it has no
81+
# information about how they were allocated.
82+
# 3. The tracemalloc built-in module provides powerful tools for understanding
83+
# the source of memory usage.
84+
# 4. tracemalloc is only available in Python 3.4 and above.

item_59_use_tracemalloc_top_n.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import tracemalloc
2+
tracemalloc.start(10) # Save up to 10 stack frames
3+
4+
time1 = tracemalloc.take_snapshot()
5+
import item_59_use_tracemalloc_waste_memory as waste_memory
6+
x = waste_memory.run()
7+
time2 = tracemalloc.take_snapshot()
8+
9+
stats = time2.compare_to(time1, 'lineno')
10+
for stat in stats[:3]:
11+
print(stat)
12+
13+
# /home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_waste_memory.py:7: size=3539 KiB (+3539 KiB), count=100000 (+100000), average=36 B
14+
# /home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_top_n.py:6: size=1264 B (+1264 B), count=2 (+2), average=632 B
15+
# <frozen importlib._bootstrap_external>:476: size=485 B (+485 B), count=6 (+6), average=81 B
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import gc
2+
found_objects = gc.get_objects()
3+
print('%d objects before' % len(found_objects))
4+
5+
6+
import item_59_use_tracemalloc_waste_memory as waste_memory
7+
x = waste_memory.run()
8+
found_objects = gc.get_objects()
9+
print('%d objects after' % len(found_objects))
10+
for obj in found_objects[:3]:
11+
print(repr(obj)[:100])
12+
13+
# 4916 objects before
14+
# 5446 objects after
15+
# <class '_ast.withitem'>
16+
# <attribute '__weakref__' of 'withitem' objects>
17+
# {'_fields': ('context_expr', 'optional_vars'), '__doc__': None, '__module__': '_ast', '__weakref__':
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
3+
def run():
4+
a = []
5+
for i in range(100000):
6+
c = i**2 + 1
7+
a.append(10 * 230 * i)
8+
return a
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import tracemalloc
2+
tracemalloc.start(10) # Save up to 10 stack frames
3+
4+
time1 = tracemalloc.take_snapshot()
5+
import item_59_use_tracemalloc_waste_memory as waste_memory
6+
x = waste_memory.run()
7+
time2 = tracemalloc.take_snapshot()
8+
9+
stats = time2.compare_to(time1, 'traceback')
10+
top = stats[0]
11+
print('\n'.join(top.traceback.format()))
12+
13+
# File "/home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_waste_memory.py", line 7
14+
# a.append(10 * 230 * i)
15+
# File "/home/robot/Documents/PycharmProjects/BetterPython59Ways/item_59_use_tracemalloc_with_trace.py", line 6
16+
# x = waste_memory.run()

0 commit comments

Comments
 (0)