How Python Manages The Memory 1691830786
How Python Manages The Memory 1691830786
Manages the
Memory
Advance Tips
60 Slide
@miladkoohi
understanding of Python memory management. When we execute
your Python script, there are so many logic runs behind in Python
memory to make the code efficient.
Memory management is very important for software developers
to work efficiently with any programming language. As we know,
Python is a famous and widely used programming language. It is
used almost in every technical domain. In contrast to a
programming language, memory management is related to
writing memory-efficient code. We cannot overlook the
importance of memory management while implementing a large
amount of data. Improper memory management leads to
slowness on the application and the server-side components. It
also becomes the reason of improper working. If the memory is
not handled well, it will take much time while preprocessing the
data.
The Stack data structure is used to store the static memory. It is only needed
inside the particular function or method call. The function is added in
program's call stack whenever we call it. Variable assignment inside the
function is temporarily stored in the function call stack; the function returns the
value, and the call stack moves to the text task. The compiler handles all these
processes, so we don't need to worry about it.
Call stack (stack data structure) holds the program's operational data such as
subroutines or function call in the order they are to be called. These functions
are popped up from the stack when we called.
Python Memory Allocation
2.Static Memory Allocation
Heap data structure is used for dynamic memory which is not related to
naming counterparts. It is type of memory that uses outside the program
at the global space. One of the best advantages of heap memory is to it
freed up the memory space if the object is no longer in use or the node is
deleted.
In the below example, we define how the function's variable store in the
stack and a heap.
Memory Management: From Hardware to Software
Memory management is the process by which applications read and write data. A
memory manager determines where to put an application’s data. Since there’s a finite
chunk of memory, like the pages in our book analogy, the manager has to find some free
space and provide it to the application. This process of providing memory is generally
called memory allocation.
On the flip side, when data is no longer needed, it can be deleted, or freed. But freed to
where? Where did this “memory” come from?
Somewhere in your computer, there’s a physical device storing data when you’re running
your Python programs. There are many layers of abstraction that the Python code goes
through before the objects actually get to the hardware though.
One of the main layers above the hardware (such as RAM or a hard drive) is the operating
system (OS). It carries out (or denies) requests to read and write memory.
Above the OS, there are applications, one of which is the default Python implementation
(included in your OS or downloaded from python.org.) Memory management for your
Python code is handled by the Python application. The algorithms and structures that the
Python application uses for memory management is the focus of this article.
The Default Python Implementation
When I first heard this, it blew my mind. A language that’s written in another language?!
Well, not really, but sort of.
The Python language is defined in a reference manual written in English. However, that
manual isn’t all that useful by itself. You still need something to interpret written code
based on the rules in the manual.
You also need something to actually execute interpreted code on a computer. The
default Python implementation fulfills both of those requirements. It converts your
Python code into instructions that it then runs on a virtual machine.
Note: Virtual machines are like physical computers, but they are
implemented in software. They typically process basic
instructions similar to Assembly instructions.
What is CPython?
CPython is the default and most widely used implementation of the Python
language. When we say Python, it essentially means we're referring to CPython.
When you download Python from python.org, you basically download the CPython
code. Thus, CPython is a program written in C language that implements all the
rules and specifications defined by the Python language.
Since CPython is the reference implementation, all new rules and specifications
of the Python language are first implemented by CPython.
Have you ever seen a .pyc file or a __pycache__ folder? That’s the
bytecode that gets interpreted by the virtual machine.
How does CPython work?
CPython is written in C, which does not natively support object-oriented
programming. Because of that, there are quite a bit of interesting designs in
the CPython code.
You may have heard that everything in Python is an object, even types
such as int and str. Well, it’s true on an implementation level in CPython.
There is a struct called a PyObject, which every other object in CPython
uses.
The PyObject, the grand-daddy of all objects in Python, contains only two
things:
The reference count is used for garbage collection. Then you have a
pointer to the actual object type. That object type is just another struct
that describes a Python object (such as a dict or int).
Objects in Python
Value
Type
Reference count
a = 100
Objects in Python
The type indicates the type of the object in CPython, and the value
field, as the name suggests, stores the value of the object (100 in
this case). We will discuss the ref_count field later .
Variables in Python
a = 100
As discussed earlier, when the above code is executed, CPython internally
creates an object of type integer. The variable a points to this integer
object as shown below:
Variables in Python
b = a
When the above code is executed, the variables a and b both point to the
same integer object, as shown below:
Variables in Python
Let's now increment the value of the integer object by 1:
When the above code is executed, CPython creates a new integer object
with the value 101 and makes variable a point to this new integer object.
Variable b will continue to point to the integer object with the value 100,
as shown below:
Variables in Python
Here, we can see that instead of overwriting the value of 100 with 101,
CPython creates a new object with the value 101 because integers in
Python are immutable. Once created, they cannot be modified. Please
note that floats and string data types are also immutable in Python.
Let's consider a simple Python program to further explain this concept:
The above code defines a simple while loop that increments the value of
the variable i until it is less than 100. When this code is executed, for every
increment of the variable i, CPython will create a new integer object with
the incremented value, and the old integer object will be deleted (to be
more precise, this object would become eligible for deletion) from
memory.
Variables in Python
CPython calls the malloc method for each new object to allocate memory
for that object. It calls the free method to delete the old object from
memory.
When the program requests memory, CPython uses the malloc method to
request that memory from the operating system, and the private heap grows in
size.
To avoid calling malloc and free for each small object creation and deletion,
CPython defines multiple allocators and deallocators for different purposes.
We’ll discuss each of them in detail in the next section!
Memory Allocators
To avoid calling the malloc and free methods frequently, CPython defines a
hierarchy of allocators, as shown below:
If the object does not have object-specific allocators, and more than 512
bytes of memory is requested, the Python memory manager directly calls
the raw memory allocator to allocate memory.
If the requested memory size is less than 512 bytes, object allocators are
used to allocate it.
Object Allocator
When a small object requests memory, instead of just allocating the memory
for that object, the object allocator requests a big block of memory from the
operating system. This big block of memory is then later used to allocate
memory for other small objects.
This way, the object allocator avoids calling malloc for each small object.
The big block of memory that the object allocator allocates is called an Arena.
Arenas are 256 KB in size.
To use Arenas efficiently, CPython divides Arena into Pools. Pools are 4 KB.
So, an arena can consists of 64 (256KB / 4KB) pools.
Object Allocator
Blocks are the smallest unit of memory that the object allocator can
allocate to an object. A block can be allocated to only one object,
and an object can only be allocated to one block. It is impossible to
place parts of an object in two or more separate blocks.
As seen in the above table, the size class 0 blocks have a size
of 8 bytes, while size class 1 blocks have a size of 16 bytes,
and so on.
A pool consists of blocks of only one size class. For example, a pool that has a block of
size class 0 cannot have blocks of any other size class.
The size of the pool is equal to the size of the virtual memory page. Here' what the
term virtual memory page means:
The term szidx indicates the size class of the pool. If szidx is 0 for a pool, it will only have
blocks of size class 0 (i.e., blocks of 8 bytes).
The term arenaindex indicates the arena to which the pool belongs.
Pools of the same size class are linked to each other using a doubly linked list. The
nextpool pointer points to the next pool of the same size class, while the prevpool pointer
points to the previous pool of the same size class.
Pools
The freeblock pointer points to the start of a singly linked list of free
blocks within the pool. When an allocated block is freed, it is inserted at
the front of the freeblock pointer.
Pools
As seen in the previous slide figure, allocated blocks are allocated to objects, while free
blocks were allocated to objects but now are free and can be allocated to new objects.
When a request is made for memory, and there are no pools with a block of the requested
size class available, CPython carves a new pool from the Arena.
When a new pool is carved, the entire pool is not immediately fragmented into blocks.
Blocks are carved from the pool as and when needed. The Blue shaded region of the pool
in the above figure indicates that these portions of the pool have not been fragmented into
blocks.
An excerpt from the CPython codebase mentions carving blocks from the pool as follows:
The available blocks in a pool are not linked all together when a pool is initialized. Instead,
only "the first two" (lowest addresses) blocks are set up, returning the first such block, and
setting pool->freeblock to a one-block list holding the second such block. This is consistent
with that pymalloc strives at all levels (arena, pool, and block) never to touch a piece of
memory until it's actually needed.
Pools
Here, we can see that when a new pool is carved from an arena, only the first two blocks
are carved from the pool. One block is allocated to the object that requested the memory,
while the other block is free or untouched, and the freeblock pointer points to this block.
CPython maintains an array called usedpools to keep track of pools in the used state
(pools available for allocations) of all the size classes.
The index of the usedpools array is equal to the size class of the pool. For each index i of
the usedpools array, usedpools[i] points to the header of the pools of size class i. For
example, usedpools[0] points to the header of pools of size class 0, and usedpools[1]
points to the header of pools of size class 1.
Pools
The figure below should make it easier to understand:
As pools of the same size class are linked to each other using a doubly linked list, all the pools
in the used state of each size class can be traversed using the usedpools array.
If usedpools[i] points to null, it means there are no pools of size class i in the used state. If an
object requests a block of size class i, CPython will carve a new pool of size class i and update
usedpools[i] to point to this new pool.
If a block is freed from a pool in full state, the pool state changes from full to used. CPython
adds this pool to the front of the doubly linked list of the pools of its size class.
Pools
As seen in the above image, poolX is of size class 0, and it is in the full
state. When a block is freed from poolX, its state changes from full to
used. Once the state of poolX becomes used, CPython adds this pool to
the front of the doubly linked list of pools of size class 0, and
usedpools[0] will start pointing to this pool.
Arenas
Arenas are big blocks of memory used for allocating memory to small objects. They are
allocated by the raw memory allocator in sizes of 256 KB.
When a small object requests memory, but there are no existing arenas to handle this request,
instead of only requesting memory for this small object, the raw memory allocator requests a
big block of memory (256 KB) from the operating system. These big blocks of memory are
called arenas.
blocks allocated.
The nfreepools term indicates the number of free pools in the arenas.
CPython maintains a doubly linked list called usable_arenas to keep track of all the arenas with
available pools.
The nextarena pointer points to the next usable arena, while the prevarena pointer points to the
previous usable arena in the usable_arenas doubly linked list. The doubly linked list is sorted in
As shown above, usable_arenas is sorted on the basis of nfreepools. Arenas with 0 free
pools is the first item, followed by arenas with 1 free pool, and so on. It means the list is
sorted with the most allocated arenas first. We'll explain why this sorting is imported in the
next section of the article.
The usable arenas list is sorted based on which ones have the most allocations, so when
there is a request for memory allocation, it is served from the arena with the most
allocations.
Does a Python Process Release Memory?
When an allocated block in a pool is freed, CPython does not return the memory back to
the operating system. This memory continues to belong to the Python process, and
CPython uses this block to allocate memory to new objects.
Even when all the blocks in a pool are freed, CPython does not return any memory from
the pool to the operating system. CPython keeps the memory of the whole pool reserved
for its own use.
CPython releases memory back to the operating system at the level of arenas, not at the
block or pool level. Also, please note that CPython releases memory of whole arenas at
once.
As memory can only be released at the arena level, CPython creates arena from new
piece of memory only when it is absolutely necessary! It always tries to allocate memory
from previously carved blocks and pools.
This is the reason usable_arenas are sorted in decreasing order of nfreepools. The next
request for memory would be allocated from arenas with the most allocations. This
allows arenas with the least data to become free if the objects they contain are deleted,
and the memory occupied by these arenas can be released to the operating system.
The Global Interpreter Lock (GIL)
In languages like C, the programmer has to manually free the memory for unused objects
(garbage collection of unused objects), while garbage collection in Python is automatically taken
care of by the language itself.
Let's first explain what a reference count is, and then we'll learn more about garbage collection
on the basis of reference counting!
Reference Count
As we saw earlier, CPython internally creates the properties type and ref count for each object.
a = "memory"
When the above code is executed, CPython creates an object memory of type string, as shown
below:
Reference Count
The field ref count indicates the number of references to the object. We know that in
Python, variables are just references to objects. In the above example, the variable a is the
only reference to the string object memory. Hence, the ref count value of string object
memory is 1.
We can get the reference count of any object in Python using the getrefcount method.
The output of the above code is 2. This indicates that the string object memory is being
referenced by two variables. However, we saw earlier that the memory object is only being
referenced by the variable a.
Why is the reference count value 2 for the string object memory when using the
getrefcount method?
Reference Count
Here, when we pass variable a to the method getrefcount, the memory object is also
referred to by the parameter var of the getrefcount method. Hence, the reference count
of the memory object is 2.
Reference Count
Thus, whenever we use the getrefcount method to get the reference count of an object, the
reference count will always be 1 more than the actual reference count of the object.
Let's create another variable, b, that points to the same string object memory:
b = a
Reference Count
Variables a and b are both pointing to the string object memory. Hence, the
reference count of the string object memory will be 2, and the output of the
b = None
The variable b is no longer pointing to the string object memory. Hence, the reference count of
the string object memory will also decrease.
Decreasing the Reference Count Using the del Keyword
We can also use the del keyword to decrease the reference count of an object.
If we assign None to the variable b (b = None), b no longer points to the string object
memory. The del keyword works the same way and is used to delete the reference of the
object, thereby reducing its reference count.
Please note that the del keyword does not delete the object.
del b
Decreasing the Reference Count Using the del Keyword
Here, we are just deleting the reference of b. The string object memory is not deleted.
Let's now get the reference count of the string object memory:
The reference count of an object increases if we assign the same object to a new variable.
When we dereference the object by using the del keyword or by making it point to None, the
reference count of that object decreases.
Now that we have a better understanding of the concept of reference count in Python, let's
learn how garbage collection works on the basis of the reference count.
Garbage Collection on the Basis of Reference Count
Garbage collection on the basis of reference count uses the reference count of the object to
release or reclaim memory. When the reference count of the object is zero, garbage
When an object is deleted from memory, it may trigger the deletion of other objects.
Ignoring the increase in the reference count due to the getrefcount method, the reference
count of the string object garbage is 2 (referenced by variable x and referenced from list b),
del x
The reference count of the string object garbage is 1 due to the array object [x, 20], as shown
below:
Garbage Collection on the Basis of Reference Count
We can still access the string object garbage with the help of variable b.
del b
Garbage collection gets triggered as soon as the reference count of the object becomes
0. This is the main garbage collection algorithm of Python, and hence, it cannot be
disabled.
Garbage collection on the basis of reference count does not work if there are cyclic
references. To free or reclaim the memory of the objects that have cyclic references,
Next, we’ll discuss cyclic references and then go deeper to understand more about the
Cyclic references are only possible with container objects, such as list, dict, and user-
defined objects. It is not possible with immutable data types, such as integers, floats, or
strings.
del b
Once we delete reference b, the array object will not be accessible from the Python
code, but it will continue to exist in memory, as shown in the above image.
As the array object is referencing itself, the reference count of the array object will never
become 0, and it will never be collected by the reference counting garbage collector.
Cyclic References in Python
Let's consider another example:
Here, we have defined two objects, object_a and object_b, of classes ClassA and ClassB,
respectively. object_a is referred to by variable A and object_b is referred to by variable B.
object_a has a property ref_b that points to object_b. Similarly, object object_b has a property ref_a
that points to object_a.
object_a and object_b can be accessed in the Python code with the help of variables A and B,
respectively.
Cyclic References in Python
Let's delete variables A and B
del A
del B
Once we delete variables A and B, object_a and object_b will not be accessible from the
The reference count of these objects will never become zero, as they point to each other (by
properties ref_a and ref_b). Hence, these objects will never be collected by the reference
As the reference count of the objects with cyclic references never becomes 0, the reference
counting garbage collection method will never clean these objects. For such cases, Python
We can use the following method to get a threshold defined for each generation:
The above output indicates that the threshold for the first generation is 700, while it is
10 for the second and third generations.
If there are more than 700 objects in the first generation, generational garbage
collection will be triggered for all objects in the first generation. If there are more than
10 objects in the 2nd generation, generational garbage collection will be triggered for
both generations 1 and 0.
We can also check the number of objects in each generation, as shown below:
gc Module in Python
Here, the number of objects in the first generation is 679, while the number of objects in the
second generation is 8, and the number of objects in the third generation is 0.
We can also run the generational garbage collection algorithm manually, as shown below:
gc.collect() will trigger the generational garbage collection. By default, it runs a full
collection. To run the generational garbage collection for 1st generations we can call this
method as shown below:
Let's check the number of objects that are still alive after the collection process,
gc Module in Python
We can also update the generational threshold for each generation, as shown below:
gc.disable()
gc.enable()
@miladkoohi
I have code experiences and my studies in the field of:
Python | Rust | Django | Flask | Clean codes | Clean
software architecture Free software | DDD | TDD | DeOps |
Data streaming