Masalah memori dapat muncul ketika sejumlah besar objek aktif dalam RAM selama pelaksanaan program, terutama jika ada batasan jumlah total memori yang tersedia.
Di bawah ini adalah ikhtisar dari beberapa metode untuk mengurangi ukuran objek, yang secara signifikan dapat mengurangi jumlah RAM yang diperlukan untuk program dalam Python murni.
Catatan: Ini adalah versi bahasa Inggris dari posting asli saya (dalam bahasa Rusia).
Untuk mempermudah, kami akan mempertimbangkan struktur dalam Python untuk mewakili titik dengan koordinat x
, y
, z
dengan akses ke nilai koordinat berdasarkan nama.
Diktik
Dalam program kecil, terutama dalam skrip, cukup sederhana dan nyaman untuk menggunakan dict
untuk mewakili informasi struktural:
>>> ob = {'x':1, 'y':2, 'z':3} >>> x = ob['x'] >>> ob['y'] = y
Dengan munculnya implementasi yang lebih ringkas di Python 3.6 dengan serangkaian kunci yang tertata, dict
telah menjadi lebih menarik. Namun, mari kita lihat ukuran jejaknya di RAM:
>>> print(sys.getsizeof(ob)) 240
Dibutuhkan banyak memori, terutama jika Anda tiba-tiba harus membuat banyak contoh:
Contoh kelas
Bagi mereka yang suka berpakaian segala sesuatu di kelas, lebih baik untuk mendefinisikan struktur sebagai kelas dengan akses berdasarkan nama atribut:
class Point: # def __init__(self, x, y, z): self.x = x self.y = y self.z = z >>> ob = Point(1,2,3) >>> x = ob.x >>> ob.y = y
Struktur instance kelas menarik:
Di sini __weakref__
adalah referensi ke daftar yang disebut referensi lemah ke objek ini, bidang __dict__
adalah referensi ke kamus instance kelas, yang berisi nilai atribut instance (perhatikan bahwa platform referensi 64-bit menempati 8 byte). Mulai dengan Python 3.3, ruang bersama digunakan untuk menyimpan kunci dalam kamus untuk semua instance kelas. Ini mengurangi ukuran jejak instance dalam RAM:
>>> print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__)) 56 112
Akibatnya, sejumlah besar instance kelas memiliki jejak yang lebih kecil dalam memori daripada kamus biasa ( dict
):
Sangat mudah untuk melihat bahwa ukuran instance dalam RAM masih besar karena ukuran kamus instance.
Instance kelas dengan __slots__
Pengurangan yang signifikan dalam ukuran instance kelas dalam RAM dicapai dengan menghilangkan __dict__
dan __weakref__
. Ini dimungkinkan dengan bantuan "trik" dengan __slots__
:
class Point: __slots__ = 'x', 'y', 'z' def __init__(self, x, y, z): self.x = x self.y = y self.z = z >>> ob = Point(1,2,3) >>> print(sys.getsizeof(ob)) 64
Ukuran objek dalam RAM menjadi lebih kecil secara signifikan:
Menggunakan __slots__
dalam definisi kelas menyebabkan jejak sejumlah besar contoh dalam memori berkurang secara signifikan:
Saat ini, ini adalah metode utama untuk secara substansial mengurangi jejak memori dari instance kelas di RAM.
Pengurangan ini dicapai oleh fakta bahwa dalam memori setelah judul objek, referensi objek disimpan - nilai atribut, dan akses ke mereka dilakukan menggunakan deskriptor khusus yang ada di kamus kelas:
>>> pprint(Point.__dict__) mappingproxy( .................................... 'x': <member 'x' of 'Point' objects>, 'y': <member 'y' of 'Point' objects>, 'z': <member 'z' of 'Point' objects>})
Untuk mengotomatiskan proses membuat kelas dengan __slots__
, ada perpustakaan [daftar nama] ( https://pypi.org/project/namedlist ). Fungsi namedlist.namedlist
membuat kelas dengan __slots__
:
>>> Point = namedlist('Point', ('x', 'y', 'z'))
Paket lain [attrs] ( https://pypi.org/project/attrs ) memungkinkan Anda untuk mengotomatiskan proses membuat kelas baik dengan dan tanpa __slots__
.
Tuple
Python juga memiliki tuple
tipe tuple
untuk mewakili struktur data yang tidak dapat diubah. Sebuah tuple adalah struktur atau catatan tetap, tetapi tanpa nama bidang. Untuk akses bidang, indeks bidang digunakan. Bidang tuple adalah sekali dan untuk semua yang terkait dengan objek nilai pada saat membuat instance tuple:
>>> ob = (1,2,3) >>> x = ob[0] >>> ob[1] = y # ERROR
Contoh tuple cukup ringkas:
>>> print(sys.getsizeof(ob)) 72
Mereka menempati 8 byte dalam memori lebih dari instance kelas dengan __slots__
, karena jejak tuple dalam memori juga berisi sejumlah bidang:
Namedtuple
Karena tuple digunakan secara luas, suatu hari ada permintaan bahwa Anda masih dapat memiliki akses ke bidang dan nama juga. Jawaban atas permintaan ini adalah module collections.namedtuple
.
Fungsi namedtuple
dirancang untuk mengotomatiskan proses menghasilkan kelas-kelas tersebut:
>>> Point = namedtuple('Point', ('x', 'y', 'z'))
Itu menciptakan subkelas tuple, di mana deskriptor didefinisikan untuk mengakses bidang dengan nama. Sebagai contoh kita, akan terlihat seperti ini:
class Point(tuple): # @property def _get_x(self): return self[0] @property def _get_y(self): return self[1] @property def _get_z(self): return self[2] # def __new__(cls, x, y, z): return tuple.__new__(cls, (x, y, z))
Semua instance dari kelas tersebut memiliki jejak memori yang identik dengan tuple. Sejumlah besar contoh meninggalkan jejak memori yang sedikit lebih besar:
Recordclass: dapat dinamai namesuple tanpa siklik GC
Karena tuple
dan, dengan demikian, namedtuple
-classes menghasilkan objek yang tidak dapat diubah dalam arti bahwa atribut ob.x
tidak lagi dapat dikaitkan dengan objek nilai lain, permintaan untuk varian nametuple yang dapat berubah telah muncul. Karena tidak ada tipe bawaan pada Python yang identik dengan tuple yang mendukung penugasan, banyak opsi telah dibuat. Kami akan fokus pada [recordclass] ( https://pypi.org/project/recordclass ), yang menerima peringkat [stackoverflow] ( https://stackoverflow.com/questions/29290359/existence-of-mutable-named- tuple-in -python / 29419745). Selain itu dapat digunakan untuk mengurangi ukuran objek dalam RAM dibandingkan dengan ukuran objek seperti tuple
..
Paket recordclass memperkenalkan tipe recordclass.mutabletuple
, yang hampir identik dengan tuple, tetapi juga mendukung penugasan. Pada dasarnya, subclass dibuat yang hampir sepenuhnya identik dengan namedtuple, tetapi juga mendukung penugasan nilai baru ke bidang (tanpa membuat instance baru). Fungsi recordclass
, seperti fungsi namedtuple
, memungkinkan Anda untuk mengotomatisasi pembuatan kelas-kelas ini:
>>> Point = recordclass('Point', ('x', 'y', 'z')) >>> ob = Point(1, 2, 3)
Instance kelas memiliki struktur yang sama dengan tuple
, tetapi hanya tanpa PyGC_Head
:
Secara default, fungsi recordclass
membuat kelas yang tidak berpartisipasi dalam mekanisme pengumpulan sampah siklik. Biasanya, namedtuple
dan recordclass
digunakan untuk menghasilkan kelas yang mewakili catatan atau struktur data sederhana (non-rekursif). Menggunakannya dengan benar di Python tidak menghasilkan referensi melingkar. Untuk alasan ini, setelah instance kelas yang dihasilkan oleh recordclass
, secara default, the
fragment is excluded, which is necessary for classes supporting the cyclic garbage collection mechanism (more precisely: in the
PyGC_Head fragment is excluded, which is necessary for classes supporting the cyclic garbage collection mechanism (more precisely: in the
structure, corresponding to the created class, in the
PyTypeObject structure, corresponding to the created class, in the
field, by default, the flag
flag field, by default, the flag
Py_TPFLAGS_HAVE_GC` tidak disetel).
Ukuran jejak memori dari sejumlah besar instance lebih kecil dari instance kelas dengan __slots__
:
Objek data
Solusi lain yang diusulkan dalam perpustakaan recordclass didasarkan pada ide: gunakan struktur penyimpanan yang sama dalam memori seperti dalam instance kelas dengan __slots__
, tetapi jangan berpartisipasi dalam mekanisme pengumpulan sampah siklik. Kelas-kelas tersebut dihasilkan menggunakan fungsi recordclass.make_dataclass
:
>>> Point = make_dataclass('Point', ('x', 'y', 'z'))
Kelas yang dibuat dengan cara ini, secara default, membuat instance yang bisa berubah.
Cara lain - gunakan deklarasi kelas dengan pewarisan dari recordclass.dataobject
:
class Point(dataobject): x:int y:int z:int
Kelas yang dibuat dengan cara ini akan membuat instance yang tidak berpartisipasi dalam mekanisme pengumpulan sampah siklik. Struktur instance dalam memori sama dengan dalam kasus dengan __slots__
, tetapi tanpa PyGC_Head
:
>>> ob = Point(1,2,3) >>> print(sys.getsizeof(ob)) 40
Untuk mengakses bidang, deskriptor khusus juga digunakan untuk mengakses bidang dengan offsetnya dari awal objek, yang terletak di kamus kelas:
mappingproxy({'__new__': <staticmethod at 0x7f203c4e6be0>, ....................................... 'x': <recordclass.dataobject.dataslotgetset at 0x7f203c55c690>, 'y': <recordclass.dataobject.dataslotgetset at 0x7f203c55c670>, 'z': <recordclass.dataobject.dataslotgetset at 0x7f203c55c410>})
Sizeo dari jejak memori sejumlah besar contoh adalah minimum yang mungkin untuk CPython:
Cython
Ada satu pendekatan berdasarkan penggunaan [Cython] ( https://cython.org ). Keuntungannya adalah bahwa bidang dapat mengambil nilai dari tipe atom bahasa C. Penjelas untuk mengakses bidang dari Python murni dibuat secara otomatis. Sebagai contoh:
cdef class Python: cdef public int x, y, z def __init__(self, x, y, z): self.x = x self.y = y self.z = z
Dalam hal ini, instans memiliki ukuran memori yang lebih kecil:
>>> ob = Point(1,2,3) >>> print(sys.getsizeof(ob)) 32
Jejak instance dalam memori memiliki struktur berikut:
Ukuran jejak sejumlah besar salinan kurang:
Namun, harus diingat bahwa ketika mengakses dari kode Python, konversi dari int
ke objek Python dan sebaliknya akan dilakukan setiap waktu.
Numpy
Menggunakan array multidimensi atau array rekaman untuk sejumlah besar data memberikan keuntungan dalam memori. Namun, untuk pemrosesan efisien dalam Python murni, Anda harus menggunakan metode pemrosesan yang fokus pada penggunaan fungsi dari paket numpy
.
>>> Point = numpy.dtype(('x', numpy.int32), ('y', numpy.int32), ('z', numpy.int32)])
Array elemen N
, diinisialisasi dengan nol, dibuat menggunakan fungsi:
>>> points = numpy.zeros(N, dtype=Point)
Ukuran array dalam memori seminimal mungkin:
Akses normal ke elemen dan baris array akan memerlukan konversi dari objek Python ke nilai C int
dan sebaliknya. Mengekstraksi satu baris menghasilkan penciptaan array yang mengandung elemen tunggal. Jejaknya tidak akan begitu kompak lagi:
>>> sys.getsizeof(points[0]) 68
Oleh karena itu, seperti disebutkan di atas, dalam kode Python, perlu untuk memproses array menggunakan fungsi dari paket numpy
.
Kesimpulan
Pada contoh yang jelas dan sederhana, dimungkinkan untuk memverifikasi bahwa komunitas pengembang dan pengguna bahasa pemrograman Python (CPython) memiliki kemungkinan nyata untuk pengurangan signifikan dalam jumlah memori yang digunakan oleh objek.