 Hello, everyone. My name is Batuan. I'm a student from Turkey. In this session, we are going to talk about C Python's memory management. C Python is the major implementation of Python language. And there are other implementations like PyPy with their own memory management. And there are implementations like Jiton and Irem Python. They use their written language memory management. For example, Jiton use JVM's memory management. This talk contains a lot of implementation details, so you can mostly find stuff in docs. Why should you learn memory management concepts? It will show you behind the curtain. For an example, you may think why Python doesn't release memory back to the system or how it finds and detects cycle references. All of them has an answer in behind the curtain. You are going to learn how to control it. Python has an interface for all internal things like abstraction texture, they mentioned, parser, and it has an interface for garbage collector. You can control it. There are helper functions like sys.getref count. You can learn stats of an object in memory. Although it seems unnecessary to learn them, Instagram increased their performance by 10% with disabling or enabling QC whenever they need. You are going to handle memory links, find them, and trace them into source with understanding of memory management. We can examine memory management in two sections, allocation and the allocation. Before we start, don't forget everything is an object in Python. So in that memory, everything can be objects. Every Python object has two common fields. One is reference count and the other one is object itself. Reference count is the count of total reference made to that object. If you set A to 10 and B to 10, you will increase 10's reference count by two. If you delete or set A and B to another object, you will decrease reference count by two. If an object's reference count reaches zero, it will immediately be allocated. But there are some cases like object reference itself and it couldn't be deducted by reference count manager, so there is a GC. And there are op type pointer. It points object itself. It contains value of object, type of object, name of object, every attribute of object. It is memory management module of C Python. In the top layer, layer three, there is Python objects and some of internal stuff. In the layer two, there is Python's object allocator. It abstracts memory and manages objects. In the layer one, there is Python's raw memory allocator. It ensures there is enough space in heap. If not, it requests memory from layer zero. Layer zero is a system allocator. If you didn't change anything and using a standard Linux distribution, it might be malloc. And in bottom layers, there are physical and low things we are not talking about. Python divides objects into sections. If an object bigger than a small object threshold, it's called as big. If not, it's called as small. Small object threshold is a static constant that defined in op malloc.c in C Python repository. It's 512 bytes by default. Big objects, it's not one of our concerns. Python direct routes big objects to a system allocator with a set of verpes. Small objects, they manage it by layer two with three levels of abstractions. The first one is blocks. They encapsulate Python objects. And pools encapsulate same sized blocks. And their size is 4 kilobytes, same as a virtual memory page. And there are arenas. They contain 64 pools, blocks, first level of abstractions. The size of block determined with 8-byte alignment notation. For an example, you have 30-byte sized object. It will be put to the 32-byte sized block. And whenever your object is allocated, Python can put another object in a range of 25 to 32, to that same block. It brings optimization. And there is size IDX value. It helps pools to contain same sized blocks. Implementation of blocks, they design for containing Python objects. They use 8-byte alignment notation for better management of free blocks. And they mark this free and link to free blocks list of their pools when their object is allocated. They encapsulate same sized blocks. Their size is 4 kilobytes. Every pool has a pool header over it. It contains a list of free blocks. Next and previous pools, the arena index of memory, the size IDX of blocks inside, and some memory stuff. Pool says free block list. The list contains free blocks. And whenever a block free, it inserts that list. There are states of blocks. Use it means it's neither empty nor full. At this one block is free, and at this one block is currently used. The full means there is no currently available blocks. And the empty means every block is empty. And it will be linked to free pools list of its arena. Arenas, they encapsulate pools. There is an overhead like pools. And they are double linked lists with next arena, previous pools. Their size is 256 kilobytes. System allocator only allocates space for arenas. And the other abstractions use space for arena space. And Python only releases memory back to the system when all pools are empty in that arena. Object specific. There are some object specific cases that are related with memory management. For example, string interning. In Python, strings are immutable seconds of unicode. So Python can reference same string twice instead of creating any string in every operation. We call it string interning. If a string is simple, it will be interred and doesn't create twice. Simple strings are escalators, digits, and underscores. For example, what one is a simple string. And this happens in compile time. So function calls and constructors doesn't affect that. And there are small integers. It's optimization for C Python. Python preallocates integers between negative 5 to 256. There are internal references to integers and it brings optimizations. There's garbage collection. Before we start talking about garbage collection, don't forget Python has not variables. It has names instead. And difference between names and variables. Names only points to an object that lives in heap. Variables contains objects value. Reference is the pointer to that object that lives in heap. And reference count, as I mentioned, the total reference is made to that object. There's good size and bad size of reference counting. It's easy to find unused objects. And it doesn't require a marking time like in mark and sweep. But there are bad sites. Every Python object contains a reference count field and it costs an object over it. It doesn't support for cyclic references. And it is one of the reasons of GIL. If GIL doesn't exist and two threads tries to increase an object's N values reference count, it will cause memory leaks. And that will be bad. And there is generational GC for cyclic references. Python runs this GC periodically. It uses an algorithm called mark and sweep. Cyclic references are references that reference itself. For an example, if you add a list itself, the list reference itself and the values inside the list reference the list. And reference counting can't be like that. So mark and sweep marks every reachable object and sweep's rest. And the reachable objects get next to the next generation. Next generations help GC to find cyclic references easily. So how to track or manage them. Python has internal stuff like GC. GC is an interface to garbage collector. There is a function here that creates a list. And the list appends itself. I call it ten times. Then I call GC.collect. It runs mark and sweep operation and returns total amount of collected objects here. And before that I disabled automatic GC. So it doesn't start without my command. Then I called it and you can see there is ten objects for it. And you can check if an object track it by garbage collector. By default, atomic types is not going to be track it. And non-atomic types is going to be track it. If you have ever written a C extension to Python or used a C module or used a Python application that consumes more memory than it needs, there might be a memory leak. So in Python 3 there is a module came. It's called traceMalloc. It traces malloc operations and helps you to find memory leaks. It's a start function. It takes frames. I'm just using one frame here. But if you have a complex code you can increase the frames amount. Then I take a snapshot. Snapshot contains relevant information about malloc. And you can take more snapshots and compare snapshots. They can compare it. Then I access the stats of a snapshot and get trace back. It's the biggest memory leak. And then I print relevant information about that. TraceMalloc is a very cool module and it has a great API for the Python. And there is debugMalloc stats. It's a raw interface of traceMalloc. This is a Python interface. And this is a raw interface of malloc. That is, you can call it with C stats, debugMalloc stats, or you can set an environment variable to something. There are different Python memory allocations. For example, malloc or PyMalloc. Or you can write your own memory allocations. Python is a flexible language. It supports new memory allocations. Any questions? That's it. Thank you for listening.