Kursus
Menulis kelas yang hemat memori di Python diperlukan untuk mengembangkan aplikasi andal yang berkinerja baik di bawah berbagai batasan sistem. Penggunaan memori yang efisien mempercepat waktu eksekusi aplikasi dan meningkatkan skalabilitas dengan mengonsumsi lebih sedikit sumber daya, yang sangat penting di lingkungan dengan ketersediaan memori terbatas.
Dalam artikel ini, saya akan memperkenalkan dasar-dasar manajemen memori di Python dan menyajikan berbagai teknik praktis untuk menulis kelas yang hemat memori. Baik Anda membangun sistem yang kompleks maupun skrip sederhana, wawasan ini akan membantu Anda menulis kode Python yang lebih bersih dan efisien.
Dasar-Dasar Manajemen Memori di Python
Sistem manajemen memori Python canggih dan dirancang untuk menyederhanakan pengembangan dengan menangani kerumitan operasi memori. Sistem ini diperlukan untuk memastikan bahwa aplikasi Python berjalan efisien.
Bagaimana Python mengelola memori
Heap privat berada di inti manajemen memori Python. Di sinilah semua objek dan struktur data Python disimpan. Pemrogram tidak dapat mengakses heap privat ini secara langsung; sebagai gantinya, mereka berinteraksi dengan objek melalui sistem manajemen memori Python.
Sistem manajemen memori menggunakan:
- Pengalokasi memori: Python menggunakan pengalokasi memori bawaan yang mengelola alokasi dan dealokasi blok memori. Pengalokasi ini dioptimalkan untuk objek kecil menggunakan “free lists,” yang mendaur ulang blok memori yang sebelumnya dialokasikan untuk mempercepat alokasi berikutnya. Untuk objek yang lebih kompleks, seperti list dan dictionary, Python menggunakan alokasi memori dinamis untuk menangani variasi ukurannya.
- Pool memori: Python mengatur memori ke dalam pool menurut ukuran objek untuk meminimalkan overhead dan fragmentasi. Ini membantu mengelola memori lebih efisien dengan mengelompokkan objek berukuran serupa.
Dealokasi memori dan garbage collection
Python menggunakan pendekatan ganda untuk garbage collection:
- Reference counting: Ini adalah metode utama di mana Python melacak referensi setiap objek. Ketika jumlah referensi suatu objek turun menjadi nol, menandakan tidak ada referensi lagi, pengalokasi memori segera membebaskan memorinya.
- Cyclic garbage collector: Python menyertakan cyclic garbage collector untuk menangani referensi melingkar, yang tidak dapat ditangani oleh reference counting saja. Kolektor ini secara berkala mengidentifikasi dan membersihkan kelompok objek yang saling mereferensikan tetapi tidak lagi digunakan di tempat lain dalam program.
Profiling dan pengoptimalan penggunaan memori
Memahami dan mengoptimalkan penggunaan memori sangat penting untuk menjaga kinerja aplikasi.
Python menawarkan beberapa alat untuk profiling memori, yang memberikan wawasan di luar debugging standar, membantu pengembang mengidentifikasi dan menyelesaikan ketidakefisienan memori:
- pympler: Alat komprehensif yang melacak penggunaan memori dan menganalisis ruang objek, cocok untuk investigasi memori yang mendalam.
- memory_profiler: Alat ini menawarkan analisis penggunaan memori baris demi baris, memungkinkan pengembang menemukan tepat baris mana yang mengonsumsi memori tinggi.
- tracemalloc: Terintegrasi dalam pustaka standar Python, tracemalloc membantu melacak alokasi memori dan mendeteksi kebocoran, memberikan wawasan tentang tren alokasi memori.
Kita akan menggunakan pustaka pympler dalam tutorial ini, dan Anda dapat memasangnya menggunakan pengelola paket pip di terminal:
pip install pympler
Anda dapat membaca lebih lanjut tentang profiling memori dari tutorial kami — Pengantar Memory Profiling di Python.
Teknik Menulis Kelas yang Hemat Memori di Python
Sekarang setelah kita memahami dasar manajemen memori di Python, mari kita bahas beberapa teknik lanjutan untuk menulis kelas yang hemat memori.
1. Mengoptimalkan struktur kelas dengan __slots__
Di Python, setiap instance kelas memiliki dictionary bernama __dict__ untuk menyimpan variabel instance. Meskipun ini memungkinkan penetapan variabel baru secara dinamis, hal ini dapat menyebabkan overhead memori yang signifikan, terutama ketika membuat banyak instance kelas. Dengan mendefinisikan __slots__, Anda memberi tahu Python untuk mengalokasikan ruang bagi sekumpulan atribut tetap, sehingga menghilangkan kebutuhan akan dictionary dinamis.
Penggunaan __slots__ memiliki dua manfaat utama:
- Penggunaan memori berkurang: Setiap objek menggunakan lebih sedikit memori tanpa dictionary instance. Ini bisa relevan ketika jutaan instance kelas diperlukan, karena penghematan per instance akan terakumulasi.
- Kecepatan akses atribut meningkat: Mengakses atribut melalui
__slots__lebih cepat daripada melalui dictionary karena pencarian atribut dilakukan langsung, melewati operasi tabel hash yang terlibat dalam akses dictionary.
Contoh praktis: Titik pada ruang dua dimensi
Pertimbangkan kelas sederhana yang merepresentasikan titik pada ruang dua dimensi:
class PointWithoutSlots:
def __init__(self, x, y):
self.x = x
self.y = y
class PointWithSlots:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
Pada kelas PointWithoutSlots, setiap instance memiliki __dict__ untuk menyimpan atribut x dan y, sedangkan kelas PointWithSlots tidak, sehingga menggunakan lebih sedikit memori per instance.
Untuk mengilustrasikan perbedaan penggunaan memori, mari kita membuat seribu titik dan mengukur total memori yang digunakan dengan paket pympler:
from pympler import asizeof
points_without_slots = [PointWithoutSlots(i, i) for i in range(1000)]
points_with_slots = [PointWithSlots(i, i) for i in range(1000)]
print("Total memory (without __slots__):", asizeof.asizeof(points_without_slots))
print("Total memory (with __slots__):", asizeof.asizeof(points_with_slots))
Keluaran yang kita lihat adalah:

Perbandingan memori dengan dan tanpa slots
Keluaran biasanya menunjukkan bahwa instance PointWithSlots mengonsumsi lebih sedikit memori dibandingkan PointWithoutSlots, menunjukkan efektivitas __slots__ dalam mengurangi overhead memori per instance.
Keterbatasan penggunaan __slots__
Meskipun __slots__ bermanfaat untuk optimalisasi memori, ada beberapa keterbatasan:
- Kurang fleksibel: Setelah
__slots__didefinisikan, Anda tidak dapat menambahkan atribut baru secara dinamis ke instance. - Komplikasi pewarisan: Jika sebuah kelas dengan
__slots__diturunkan, subclass juga harus mendefinisikan__slots__untuk terus mendapatkan manfaat optimalisasi memori, yang dapat mempersulit desain kelas.
Terlepas dari keterbatasan ini, penggunaan __slots__ merupakan alat yang kuat bagi pengembang yang ingin mengoptimalkan penggunaan memori dalam aplikasi Python, terutama di lingkungan dengan batasan memori ketat atau ketika jumlah instance sangat banyak.
2. Menerapkan pola desain yang hemat memori
Pola desain yang hemat memori adalah solusi arsitektural yang membantu mengoptimalkan penggunaan dan pengelolaan memori dalam aplikasi Python.
Dua pola yang sangat efektif untuk efisiensi memori dalam desain kelas adalah pola Flyweight dan Singleton. Ketika diimplementasikan dengan benar pada skenario yang melibatkan banyak instance kelas atau data bersama, pola-pola ini dapat secara drastis mengurangi konsumsi memori.
Pola desain Flyweight
Pola desain Flyweight bekerja dengan membagikan bagian state yang umum di antara banyak objek alih-alih menyimpan semua data di setiap objek, sehingga secara signifikan mengurangi jejak memori keseluruhan.
Pertimbangkan kelas yang merepresentasikan lingkaran grafis dalam aplikasi menggambar, di mana setiap lingkaran memiliki warna, jari-jari, dan koordinat. Dengan pola Flyweight, warna dan jari-jari, yang kemungkinan dibagikan banyak lingkaran, dapat disimpan dalam objek bersama:
class Circle:
# Flyweight object
def __init__(self, color):
self.color = color
def draw(self, radius, x, y):
print(f"Drawing a {self.color} circle with radius {radius} at ({x},{y})")
class CircleFactory:
# Factory to manage Flyweight objects
_circles = {}
@classmethod
def get_circle(cls, color):
if not cls._circles.get(color):
cls._circles[color] = Circle(color)
return cls._circles[color]
Dalam contoh ini, beberapa instance lingkaran berbagi objek Circle dari CircleFactory berdasarkan warna, memastikan memori digunakan lebih efisien dengan menyimpan data warna satu kali.
Mari gunakan kelas ini untuk membuat instance dan memahami bagaimana memori dihemat.
import sys
factory = CircleFactory()
circle1 = factory.get_circle("Red")
circle2 = factory.get_circle("Red")
circle3 = factory.get_circle("Blue")
print("Memory address of circle1:", id(circle1))
print("Memory address of circle2:", id(circle2))
print("Memory address of circle3:", id(circle3))
print ("-----------------------------")
print("Memory used by circle1:", sys.getsizeof(circle1))
print("Memory used by circle2:", sys.getsizeof(circle2))
print("Memory used by circle3:", sys.getsizeof(circle3))
Keluaran yang kita lihat adalah:

Penggunaan memori untuk pola desain flyweight
Kita dapat melihat bahwa circle1 dan circle2 memiliki alamat memori yang sama, yang berarti keduanya memang objek yang sama (berbagi instance yang sama untuk warna “Red”). circle3 memiliki alamat memori berbeda karena merepresentasikan lingkaran dengan warna berbeda (“Blue”).
circle1 dan circle2 yang merupakan instance yang sama menunjukkan bahwa CircleFactory berhasil membagikan instance Circle di antara keduanya. Di sinilah efisiensi memori muncul: jika Anda memiliki seribu lingkaran merah dalam aplikasi menggunakan pola Flyweight, semuanya akan berbagi satu instance Circle untuk warna tersebut, alih-alih masing-masing memiliki instance Circle terpisah, sehingga menghemat banyak memori.
Pola desain Singleton
Pola Singleton memastikan bahwa suatu kelas hanya memiliki satu instance dan menyediakan titik akses global ke instance tersebut. Pola ini berguna untuk mengelola konfigurasi atau sumber daya bersama di seluruh aplikasi, mengurangi overhead memori dengan tidak menduplikasi sumber daya bersama atau data konfigurasi di banyak tempat.
Pertimbangkan kelas Database berikut:
class Database:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Database, cls).__new__(cls)
# Initialize any attributes of the instance here
return cls._instance
def connect(self):
# method to simulate database connection
return "Connection established"
Dalam implementasi ini, kelas Database memastikan bahwa berapa pun kali diinstansiasi, hanya satu instance bersama yang digunakan, menghemat memori dengan menghindari banyak objek identik.
Kita dapat memeriksanya dengan kode berikut:
db1 = Database()
print(db1.connect())
db2 = Database()
print(db1 is db2)
Keluaran sebagai berikut:

Pembuatan instance untuk pola desain singleton
Keluaran “True” menunjukkan bahwa variabel db1 dan db2 mengacu pada instance yang sama.
Pola-pola ini menunjukkan bagaimana desain yang matang dapat meningkatkan efisiensi memori secara signifikan. Dengan memilih dan menerapkan pola desain yang sesuai, pengembang dapat mengoptimalkan aplikasi Python agar lebih ramping dan berkinerja tinggi.
3. Menggunakan tipe data bawaan dalam kelas
Memilih tipe data bawaan di Python yang tepat dapat sangat memengaruhi efisiensi memori. Tipe bawaan dioptimalkan untuk kinerja dan penggunaan memori berkat implementasi internalnya di interpreter Python.
Misalnya, karena sifatnya yang unik, tuple dan set dapat menjadi pilihan yang lebih hemat memori untuk kasus penggunaan tertentu dibandingkan list dan dictionary.
Tuple umumnya menggunakan lebih sedikit memori daripada list karena berukuran tetap dan tidak dapat diubah setelah dibuat, memungkinkan mekanisme internal Python mengoptimalkan penyimpanannya di memori. Untuk kelas yang dirancang menangani data yang tidak perlu diubah setelah dibuat, tuple adalah pilihan yang sangat baik.
Contoh praktis: Kelas poligon
Pertimbangkan dua kelas Poligon sederhana yang masing-masing menggunakan tuple dan list untuk menyimpan nilai:
class PolygonWithTuple:
def __init__(self, points):
self.points = tuple(points) # Storing points as a tuple
def display_points(self):
return self.points
class PolygonWithList:
def __init__(self, points):
self.points = list(points) # Storing points as a list
def display_points(self):
return self.points
Kita dapat membuat instance dari kedua kelas untuk membandingkan penggunaan memori:
from pympler import asizeof
points = [(1, 2), (3, 4), (5, 6), (7, 8)]
# Creating instances of each class
polygon_tuple = PolygonWithTuple(points)
polygon_list = PolygonWithList(points)
# Memory usage comparison using pympler
print("Total memory used by Polygon with Tuple:", asizeof.asizeof(polygon_tuple))
print("Total memory used by Polygon with List:", asizeof.asizeof(polygon_list))
Keluaran yang kita dapatkan adalah:

Perbandingan memori untuk tuple dan list
Keluaran ini menunjukkan bahwa tuple biasanya menggunakan lebih sedikit memori daripada list, menjadikan kelas PolygonWithTuple lebih hemat memori untuk menyimpan data tetap dan tak berubah seperti koordinat geometri.
4. Memanfaatkan generator untuk evaluasi malas (lazy evaluation)
Di Python, generator adalah alat yang kuat untuk mengelola memori secara efisien, terutama saat bekerja dengan dataset besar.
Generator memungkinkan data diproses satu per satu, hanya memuat apa yang diperlukan pada saat itu ke dalam memori. Metode lazy evaluation ini bermanfaat pada aplikasi yang menangani data dalam jumlah besar, tetapi hanya sebagian kecil yang perlu diproses pada satu waktu, sehingga mengurangi jejak memori keseluruhan.
Contoh praktis: Memproses dataset besar
Pertimbangkan aplikasi yang perlu memproses dataset besar berisi catatan pengguna, misalnya menghitung usia rata-rata pengguna. Alih-alih memuat semua catatan ke dalam list (yang dapat mengonsumsi banyak memori), generator dapat memproses satu catatan pada satu waktu.
def process_records(file_name):
"""Generator that yields user ages from a file one at a time."""
with open(file_name, 'r') as file:
for line in file:
user_data = line.strip().split(',')
yield int(user_data[1])
def average_age(file_name):
"""Calculate the average age from user records."""
total_age = 0
count = 0
for age in process_records(file_name):
total_age += age
count += 1
return total_age / count if count else 0
print("Average Age:", average_age('user_data.csv'))
Dalam contoh ini, fungsi generator process_records() membaca file satu baris dalam satu waktu, mengonversi data relevan menjadi integer, dan me-return nilai tersebut secara bertahap (yield). Ini berarti hanya data satu baris yang ditahan di memori pada suatu waktu, meminimalkan penggunaan memori. Fungsi average_age() mengonsumsi generator tersebut, mengakumulasi total usia dan jumlah tanpa pernah membutuhkan seluruh dataset di memori sekaligus.
Dengan demikian, menggunakan pendekatan lazy evaluation, kita dapat menghemat memori serta meningkatkan responsivitas dan skalabilitas aplikasi.
5. Menghindari referensi melingkar
Referensi melingkar terjadi ketika dua atau lebih objek saling mereferensikan. Hal ini dapat terjadi secara halus dengan struktur seperti list, dictionary, atau definisi kelas yang lebih kompleks di mana instance saling menyimpan referensi, membentuk loop.
Seperti yang kita lihat pada dasar manajemen memori, Python memiliki garbage collector tambahan untuk mendeteksi dan membersihkan referensi melingkar. Namun, mengandalkan mekanisme ini dapat menyebabkan peningkatan penggunaan memori dan ketidakefisienan:
- Pembersihan tertunda: Cyclic garbage collector berjalan secara berkala, bukan terus-menerus. Ini berarti objek yang terlibat dalam referensi melingkar mungkin tidak segera dibersihkan setelah tidak lagi dibutuhkan, sehingga memori terpakai secara tidak perlu.
- Overhead kinerja: Cyclic garbage collector memerlukan sumber daya tambahan untuk secara berkala memeriksa dan membersihkan referensi melingkar, yang dapat memengaruhi kinerja.
Contoh praktis: Struktur data pohon
Pertimbangkan kasus penggunaan umum pada struktur data mirip pohon di mana setiap node menyimpan referensi ke anak-anaknya dan opsional ke induknya. Keterkaitan induk-anak ini dapat dengan mudah membuat referensi melingkar.
class TreeNode:
def __init__(self, value, parent=None):
self.value = value
self.parent = parent
self.children = []
def add_child(self, child):
child.parent = self # Set parent reference
self.children.append(child)
root = TreeNode("root")
child = TreeNode("child1", root)
root.add_child(child)
Dalam contoh ini, root mereferensikan child melalui list children, dan child mereferensikan root melalui atribut parent. Pengaturan ini dapat menciptakan referensi melingkar tanpa intervensi lebih lanjut.
Menggunakan weak reference untuk menghindari referensi melingkar
Weak reference adalah salah satu cara efektif untuk memutus atau mencegah referensi melingkar tanpa mendesain ulang seluruh struktur data.
Modul weakref di Python memungkinkan referensi ke objek yang tidak meningkatkan jumlah referensinya. Weak reference ini tidak mencegah objek yang direferensikan untuk digarbage-collect, sehingga mencegah kebocoran memori yang terkait dengan referensi melingkar.
Berikut cara kita memodifikasi kelas sebelumnya untuk menggunakan weak reference:
import weakref
class TreeNode:
def __init__(self, value, parent=None):
self.value = value
self.parent = weakref.ref(parent) if parent else None
self.children = []
def add_child(self, child):
child.parent = weakref.ref(self)
self.children.append(child)
root = TreeNode("root")
child = TreeNode("child1", root)
root.add_child(child)
Dalam kelas ini, atribut parent sekarang adalah weak reference ke node induk. Ini berarti node induk dapat digarbage-collect meskipun node anak masih ada, selama tidak ada referensi kuat lain ke induk.
Pendekatan ini secara efisien mencegah masalah kebocoran memori yang biasanya terkait dengan referensi melingkar dalam struktur semacam ini.
Kesimpulan
Artikel ini memperkenalkan dasar-dasar manajemen memori di Python dan menyajikan lima teknik lanjutan untuk menulis kelas yang hemat memori, lengkap dengan contoh praktis dan perbandingan memori. Kami berharap Anda menerapkan teknik dan praktik terbaik ini dalam proyek Anda untuk membangun aplikasi yang skalabel dan efisien.
Kami mendorong Anda untuk melihat track Python Programming kami, yang mencakup prinsip rekayasa perangkat lunak dan pemrograman berorientasi objek, termasuk menulis kode Python yang efisien.
Sebagai senior data scientist, saya merancang, mengembangkan, dan menerapkan solusi machine learning berskala besar untuk membantu bisnis membuat keputusan yang lebih baik berbasis data. Sebagai penulis di bidang data science, saya berbagi pembelajaran, saran karier, dan tutorial praktis yang mendalam.
FAQs
Apa itu __slots__, dan bagaimana cara kerjanya membantu menulis kelas Python yang hemat memori?
__slots__ adalah mekanisme di Python yang memungkinkan deklarasi eksplisit atribut instance, mencegah pembuatan dictionary instance. Dengan mendefinisikan __slots__ dalam sebuah kelas, Python menggunakan struktur data yang jauh lebih hemat memori dibandingkan dictionary per instance seperti biasa. Ini mengurangi penggunaan memori, terutama pada program yang membuat banyak instance kelas.
Bagaimana referensi melingkar memengaruhi penggunaan memori, dan bagaimana cara menghindarinya?
Referensi melingkar terjadi ketika dua atau lebih objek saling mereferensikan, menciptakan loop yang mencegah garbage collector Python mereklamasi memori mereka. Untuk menghindari referensi melingkar, Anda dapat menggunakan weak reference melalui modul weakref, merombak desain untuk menghapus referensi yang tidak perlu, atau memutus siklus secara manual dengan menyetel referensi menjadi None saat tidak lagi diperlukan.
Apa peran garbage collector dalam mengelola memori di Python?
Garbage collector di Python melengkapi sistem reference counting dengan mendeteksi dan membuang referensi melingkar. Ia secara berkala memindai kelompok objek yang hanya dapat diakses oleh satu sama lain dan tidak digunakan di mana pun di aplikasi, sehingga membebaskan memori yang tidak dapat direklamasi hanya dengan reference counting.
Mengapa penggunaan tipe dan struktur data bawaan penting untuk efisiensi memori?
Tipe dan struktur data bawaan Python sangat dioptimalkan untuk kinerja dan penggunaan memori. Menggunakan tipe bawaan ini, seperti tuple alih-alih list untuk data yang tidak berubah (immutable) atau menggunakan set untuk koleksi unik, dapat secara signifikan mengurangi overhead memori dibandingkan struktur data kustom atau yang kurang sesuai.
Bagaimana alat profiling memori membantu menulis kelas Python yang hemat memori?
Alat profiling memori seperti memory_profiler, pympler, dan tracemalloc membantu pengembang memahami bagaimana memori digunakan dalam aplikasi mereka. Alat ini dapat mengidentifikasi kebocoran memori, struktur data yang tidak efisien, atau baris kode spesifik yang menggunakan memori berlebihan, sehingga memungkinkan pengembang memutuskan di mana optimalisasi diperlukan.

