Setiap instance dari sebuah kelas di CPython yang dibuat menggunakan sintaks kelas terlibat dalam mekanisme pengumpulan sampah melingkar . Ini meningkatkan jejak memori setiap instance dan dapat menyebabkan masalah memori pada sistem yang sarat muatan.
Jika perlu, apakah mungkin untuk mengeluarkan satu mekanisme penghitungan tautan dasar?
Mari kita menganalisis satu pendekatan yang akan membantu untuk membuat kelas yang instansnya akan dihapus hanya menggunakan mekanisme penghitungan tautan .
Sedikit tentang pengumpulan sampah di CPython
Mekanisme utama untuk pengumpulan sampah di Python adalah mekanisme penghitungan tautan. Setiap objek berisi bidang yang berisi nilai saat ini dari jumlah tautan ke sana. Objek dihancurkan segera setelah nilai penghitung referensi menjadi nol. Namun, itu tidak memungkinkan membuang objek yang berisi referensi melingkar. Sebagai contoh
lst = [] lst.append(lst) del lst
Dalam kasus seperti itu, setelah menghapus objek, penghitung referensi tetap lebih dari nol. Untuk mengatasi masalah ini, Python memiliki mekanisme tambahan yang melacak objek dan memecah loop di grafik tautan antara objek. Ada artikel bagus tentang cara mekanisme pengumpulan sampah bekerja di CPython3.
Overhead terkait dengan mekanisme pengumpulan sampah
Biasanya, mekanisme pengumpulan sampah tidak menimbulkan masalah. Tetapi ada overhead tertentu yang terkait dengannya:
Ketika setiap kelas memori dialokasikan, header PyGC_Head ditambahkan : (setidaknya 24 byte dalam Python <= 3,7 dan setidaknya 16 byte dalam 3,8 pada platform 64-bit.
Ini dapat membuat masalah kekurangan memori jika Anda menjalankan banyak contoh dari proses yang sama, di mana Anda harus memiliki sejumlah besar objek dengan jumlah atribut yang relatif kecil, dan ukuran memori terbatas.
Apakah terkadang membatasi diri pada mekanisme dasar penghitungan tautan?
Mekanisme pengumpulan sampah bundar mungkin berlebihan ketika kelas mewakili tipe data non-rekursif. Misalnya, catatan yang berisi nilai-nilai tipe sederhana (angka, string, tanggal / waktu). Untuk menggambarkan, pertimbangkan kelas sederhana:
class Point: x: int y: int
Jika digunakan dengan benar, siklus tautan tidak dimungkinkan. Meskipun dengan Python, tidak ada yang mencegah "tendang kaki Anda":
p = Point(0, 0) px = p
Namun demikian, untuk kelas Point
, seseorang dapat membatasi dirinya pada mekanisme penghitungan tautan. Tetapi belum ada mekanisme standar untuk menolak pengumpulan sampah siklik untuk satu kelas.
CPython modern dirancang sehingga ketika mendefinisikan kelas kustom dalam struktur yang bertanggung jawab untuk tipe yang mendefinisikan kelas kustom, bendera Py_TPFLAGS_HAVE_GC selalu ditetapkan. Ini menentukan bahwa instance kelas akan dimasukkan dalam mekanisme pengumpulan sampah. Untuk semua objek seperti itu, ketika dibuat, header PyGC_Head ditambahkan , dan mereka termasuk dalam daftar objek yang dipantau. Jika flag Py_TPFLAGS_HAVE_GC
tidak disetel, maka hanya mekanisme penghitungan tautan dasar yang berfungsi. Namun, satu Py_TPFLAGS_HAVE_GC
ulang Py_TPFLAGS_HAVE_GC
tidak akan berfungsi. Anda perlu membuat perubahan pada inti CPython yang bertanggung jawab untuk membuat dan menghancurkan instance. Dan ini masih bermasalah.
Tentang satu implementasi
Sebagai contoh implementasi ide, pertimbangkan objek data kelas dasar dari proyek recordclass . Dengan menggunakannya, Anda bisa membuat kelas yang Py_TPFLAGS_HAVE_GC
tidak berpartisipasi dalam mekanisme pengumpulan sampah melingkar ( Py_TPFLAGS_HAVE_GC
tidak diinstal dan, karenanya, tidak ada header PyGC_Head
tambahan). Mereka memiliki struktur yang persis sama dalam memori seperti instance kelas dengan __slots__ , tetapi tanpa PyGC_Head
:
from recordclass import dataobject class Point(dataobject): x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 32
Sebagai perbandingan, kami memberikan kelas yang mirip dengan __slots__
:
class Point: __slots__ = 'x', 'y' x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 64
Perbedaan ukurannya persis dengan ukuran header PyGC_Head
. Untuk contoh dengan beberapa atribut, peningkatan ukuran jejaknya dalam RAM mungkin signifikan. Untuk instance kelas Point
, menambahkan PyGC_Head
meningkatkan ukurannya sebanyak 2 kali.
Untuk mencapai efek ini, datatype
metaclass khusus datatype
, yang menyediakan pengaturan subkelas dataobject
. Sebagai hasil dari konfigurasi, flag Py_TPFLAGS_HAVE_GC
, ukuran dasar instance tp_basicsize bertambah dengan jumlah yang diperlukan untuk menyimpan slot bidang tambahan. Nama bidang yang sesuai dicantumkan saat kelas dideklarasikan (kelas Point
memiliki dua: x
dan y
). Tipe datatype
metatlass juga menyediakan pengaturan nilai slot tp_alloc , tp_new , tp_dealloc , tp_free , yang menerapkan algoritma yang benar untuk membuat dan menghancurkan instance dalam memori. Secara default, instance tidak memiliki __weakref__ dan __dict__ (serta instance kelas dengan __slots__
).
Kesimpulan
Seperti yang dapat dilihat, dalam CPython, jika perlu, dimungkinkan untuk menonaktifkan mekanisme pengumpulan sampah siklik untuk kelas tertentu, ketika ada keyakinan bahwa turunannya tidak akan membentuk tautan sirkuler. Ini akan mengurangi jejak mereka dalam memori dengan ukuran header PyGC_Head
.