Manajemen memori python

Pernahkah Anda bertanya-tanya bagaimana data Anda bekerja dengan terlihat di perut Python? Tentang bagaimana variabel dibuat dan disimpan dalam memori? Bagaimana dan kapan mereka dihapus? Materi, terjemahan yang kami terbitkan, dikhususkan untuk meneliti kedalaman Python, di mana kami akan mencoba mencari tahu fitur-fitur manajemen memori dalam bahasa ini. Setelah mempelajari artikel ini, Anda akan memahami bagaimana mekanisme komputer tingkat rendah bekerja, terutama yang terkait dengan memori. Anda akan memahami bagaimana Python mengabstraksi operasi tingkat rendah dan bagaimana ia mengelola memori.



Mengetahui apa yang terjadi dengan Python akan memungkinkan Anda untuk lebih memahami beberapa perilaku bahasa ini. Saya harap, ini akan memberi Anda kesempatan untuk menghargai pekerjaan luar biasa yang sedang dilakukan di dalam implementasi bahasa yang Anda gunakan sehingga program Anda bekerja persis seperti yang Anda butuhkan.

Memori adalah buku kosong


Memori komputer, pada awal bekerja dengannya, dapat direpresentasikan dalam bentuk buku kosong yang dimaksudkan untuk cerita pendek. Meskipun tidak ada apa-apa di halamannya, tetapi segera penulis cerita akan muncul, masing-masing ingin menulis kisahnya sendiri di buku ini.

Karena satu cerita tidak dapat ditulis di atas yang lain, penulis perlu berhati-hati tentang halaman buku yang mereka tulis. Sebelum menulis apa pun, mereka berkonsultasi dengan pemimpin redaksi. Dia memutuskan di mana tepatnya penulis dapat merekam cerita.

Karena buku yang kita bicarakan sudah ada sejak lama, banyak cerita di dalamnya sudah ketinggalan jaman. Jika tidak ada yang membaca cerita atau menyebutkannya dalam karya-karya mereka, cerita ini dihapus dari buku, membuat ruang untuk cerita baru.

Secara umum, kita dapat mengatakan bahwa memori komputer sangat mirip dengan buku semacam itu. Bahkan, blok memori berkelanjutan yang panjangnya tetap bahkan disebut halaman, jadi kami percaya bahwa membandingkan memori dengan buku sangat sukses.

Penulis yang menulis cerita mereka di buku adalah aplikasi atau proses berbeda yang perlu menyimpan data dalam memori. Pemimpin redaksi, yang memutuskan halaman buku mana yang bisa penulis tulis, adalah mekanisme yang berhubungan dengan manajemen memori. Dan orang yang menghilangkan cerita lama dari buku, membuat ruang untuk yang baru, dapat dibandingkan dengan mekanisme pengumpulan sampah.

Manajemen Memori: Jalan dari Besi ke Program


Manajemen memori adalah suatu proses, selama implementasi program mana yang menulis data ke memori dan membacanya dari sana. Manajer memori adalah entitas yang menentukan di mana tepatnya suatu aplikasi dapat menempatkan datanya dalam memori. Karena jumlah fragmen memori yang dapat dialokasikan untuk aplikasi tidak terbatas, seperti halnya jumlah halaman dalam buku apa pun tidak terbatas, manajer memori, yang melayani aplikasi, perlu menemukan fragmen memori yang bebas dan menyediakannya untuk aplikasi. Proses ini, di mana memori dialokasikan untuk aplikasi, disebut alokasi memori.

Di sisi lain, ketika beberapa data tidak lagi dibutuhkan, itu dapat dihapus, atau, dengan kata lain, membebaskan memori yang ditempati. Tetapi apa sebenarnya yang mereka โ€œisolasiโ€ dan โ€œbebaskanโ€ ketika berbicara tentang ingatan?

Di suatu tempat di komputer Anda ada perangkat fisik yang menyimpan data yang digunakan oleh program Python saat mereka bekerja. Sebelum objek Python muncul dalam memori fisik, kode harus melalui banyak lapisan abstraksi.

Salah satu lapisan utama seperti itu, yang terletak di atas perangkat keras (seperti RAM atau hard disk) adalah sistem operasi (OS). Itu mengeksekusi (atau menolak untuk memenuhi) permintaan untuk membaca data dari memori dan untuk menulis data ke memori.

Ada aplikasi di atas OS, dalam kasus kami, salah satu implementasi Python (mungkin paket perangkat lunak yang merupakan bagian dari OS Anda atau diunduh dari python.org ). Ini adalah paket perangkat lunak ini yang terlibat dalam manajemen memori, memastikan pengoperasian kode Python Anda. Fokus artikel ini adalah pada algoritma dan struktur data yang digunakan Python untuk mengelola memori.

Implementasi referensi python


Referensi implementasi Python disebut CPython. Itu ditulis dalam C. Ketika saya pertama kali mendengarnya, itu benar-benar membuat saya gelisah. Bahasa pemrograman yang ditulis dalam bahasa lain? Sebenarnya ini tidak sepenuhnya benar.

Spesifikasi Python dijelaskan dalam bahasa Inggris biasa dalam dokumen ini . Namun, spesifikasi ini saja, kode yang ditulis dengan Python, tentu saja, tidak dapat dijalankan. Untuk melakukan ini, Anda memerlukan sesuatu yang, mengikuti aturan spesifikasi ini, dapat menafsirkan kode yang ditulis dengan Python.

Selain itu, Anda memerlukan sesuatu yang dapat mengeksekusi kode yang ditafsirkan di komputer. Referensi implementasi Python memecahkan kedua tugas ini. Itu mengubah kode menjadi instruksi yang kemudian dieksekusi di mesin virtual.

Mesin virtual mirip dengan komputer biasa yang terbuat dari silikon, logam dan bahan lainnya, tetapi mereka diimplementasikan dalam perangkat lunak. Mereka biasanya sibuk memproses instruksi dasar, mirip dengan instruksi yang ditulis dalam Assembler .

Python adalah bahasa yang ditafsirkan. Kode yang ditulis dengan Python dikompilasi menjadi seperangkat instruksi yang nyaman bagi komputer untuk digunakan, dalam kode byte yang disebut. Instruksi ini ditafsirkan oleh mesin virtual ketika Anda menjalankan program Anda.

Pernahkah Anda melihat file dengan ekstensi .pyc atau folder __pycache__ ? Mereka mengandung bytecode yang sama yang ditafsirkan oleh mesin virtual.

Penting untuk dicatat bahwa selain CPython, ada implementasi Python lainnya. Misalnya, saat menggunakan IronPython, kode Python dikompilasi menjadi pernyataan Microsoft CLR. Dalam Jython, kode dikompilasi ke dalam bytecode Java dan dieksekusi dalam mesin virtual Java. Di dunia Python, ada yang namanya PyPy , tetapi layak untuk artikel yang terpisah, jadi di sini kita hanya menyebutkannya.

Untuk tujuan artikel ini, saya akan fokus pada bagaimana mekanisme manajemen memori bekerja dalam implementasi referensi Python - CPython.

Perlu dicatat bahwa meskipun sebagian besar dari apa yang akan kita bicarakan di sini akan berlaku untuk versi baru Python, banyak hal dapat berubah di masa depan. Oleh karena itu, perhatikan fakta bahwa dalam artikel ini saya fokus pada versi terbaru dari Python pada saat penulisan - Python 3.7 .

Jadi, paket perangkat lunak CPython ditulis dalam C, ia mengartikan bytecode Python. Apa hubungannya ini dengan manajemen memori? Faktanya adalah bahwa algoritma dan struktur data yang digunakan untuk manajemen memori ada dalam kode CPython yang ditulis, seperti yang telah dikatakan, dalam C. Untuk memahami bagaimana manajemen memori bekerja di Python, Anda harus terlebih dahulu memahami sedikit tentang CPython.

Bahasa C di mana CPython ditulis tidak memiliki dukungan bawaan untuk pemrograman berorientasi objek. Karena itu, banyak solusi arsitektur yang menarik digunakan dalam kode CPython.

Anda mungkin pernah mendengar bahwa segala sesuatu di Python adalah objek, bahkan tipe data primitif seperti int dan str . Dan ini memang kasus di tingkat implementasi bahasa di CPython. Ada struktur yang disebut PyObject , yang digunakan oleh objek yang dibuat dalam CPython.

Struktur adalah tipe data komposit yang dapat mengelompokkan data dari berbagai jenis. Jika Anda membandingkan ini dengan pemrograman berorientasi objek, maka strukturnya mirip dengan kelas yang memiliki atribut tetapi tidak ada metode.

PyObject adalah nenek moyang dari semua objek Python. Struktur ini hanya berisi dua bidang:

  • ob_refcnt - penghitung referensi.
  • ob_type - penunjuk ke tipe lain.

Penghitung referensi digunakan untuk menerapkan mekanisme pengumpulan sampah. Bidang PyObject lain adalah pointer ke tipe objek tertentu. Tipe ini diwakili oleh struktur lain yang menggambarkan objek Python (misalnya, itu bisa berupa tipe dict atau int ).

Setiap objek memiliki sendiri, unik untuk objek seperti itu, mekanisme alokasi memori, yang tahu bagaimana mendapatkan memori yang diperlukan untuk menyimpan objek ini. Selain itu, setiap objek memiliki mekanisme sendiri untuk membebaskan memori, yang "membebaskan" memori setelah tidak lagi diperlukan.

Namun, perlu dicatat bahwa dalam semua percakapan tentang alokasi dan pelepasan memori ini, ada satu faktor penting. Faktanya adalah bahwa memori komputer adalah sumber daya bersama. Jika, pada saat yang sama, dua proses berbeda mencoba menulis sesuatu ke area memori yang sama, sesuatu yang buruk dapat terjadi.

Kunci Global Juru Bahasa


Global Interpreter Lock (GIL) adalah solusi untuk masalah umum yang terjadi saat bekerja dengan sumber daya komputer bersama seperti memori. Ketika dua utas mencoba untuk memodifikasi sumber daya yang sama secara bersamaan, mereka dapat "bertabrakan" satu sama lain. Hasilnya akan berantakan dan tidak satu pun aliran akan mencapai apa yang diperjuangkannya.

Mari kita kembali ke analogi buku lagi. Bayangkan bahwa dua penulis secara sewenang-wenang memutuskan bahwa sekarang giliran mereka untuk membuat catatan. Tetapi mereka juga memutuskan untuk mencatat secara bersamaan di halaman yang sama.

Masing-masing dari mereka tidak memperhatikan fakta bahwa yang lain mencoba untuk menulis ceritanya. Bersama-sama mereka mulai menulis teks pada halaman. Akibatnya, dua cerita akan direkam di sana, satu di atas yang lain, yang akan membuat halaman tersebut benar-benar tidak dapat dibaca.

Salah satu solusi untuk masalah ini adalah mekanisme juru bahasa global tunggal yang memblokir sumber daya bersama yang bekerja sama dengan utas tertentu. Dalam contoh kami, ini adalah "mekanisme" yang "memblokir" halaman buku. Mekanisme seperti itu menghilangkan situasi yang dijelaskan di atas, di mana dua penulis secara bersamaan menulis teks pada halaman yang sama.

Mekanisme GIL dalam Python menyelesaikan ini dengan memblokir seluruh penerjemah. Akibatnya, tidak ada yang dapat mengganggu pengoperasian utas saat ini. Dan ketika CPython bekerja dengan memori, ia menggunakan GIL untuk memastikan bahwa pekerjaan ini dilakukan dengan aman dan efisien.

Ada kekuatan dan kelemahan untuk pendekatan ini, dan GIL adalah subjek perdebatan sengit di komunitas Python. Untuk mempelajari lebih lanjut tentang GIL, Anda dapat melihat materi ini .

Pengumpulan sampah


Mari kita kembali ke analogi buku dan membayangkan bahwa beberapa kisah yang dicatat dalam buku ini sudah ketinggalan zaman. Tidak ada yang membacanya, tidak ada yang menyebutkannya di mana saja. Dan jika tidak ada yang membaca atau merujuk pada beberapa bahan dalam karya mereka, maka bahan ini dapat dibuang, memberi ruang bagi teks-teks baru.

Ini, dongeng yang terlupakan, dapat dibandingkan dengan objek Python yang jumlah referensi nya nol. Ini adalah penghitung yang sama yang kami bicarakan ketika membahas struktur PyObject .

Penghitung tautan bertambah karena beberapa alasan. Misalnya, penghitung bertambah jika objek yang disimpan dalam satu variabel ditulis ke variabel lain:

 numbers = [1, 2, 3] #   = 1 more_numbers = numbers #   = 2 

Itu meningkat ketika objek diteruskan ke beberapa fungsi sebagai argumen:

 total = sum(numbers) 

Dan di sini adalah contoh lain dari situasi di mana jumlah di penghitung referensi meningkat. Ini terjadi jika objek termasuk dalam daftar:

 matrix = [numbers, numbers, numbers] 

Python memungkinkan programmer untuk mengetahui nilai saat ini dari jumlah referensi objek tertentu menggunakan modul sys . Untuk ini, konstruksi berikut digunakan:

 sys.getrefcount(numbers) 

getfefcount() , Anda harus ingat bahwa meneruskan objek ke metode getfefcount() meningkatkan nilai penghitung sebesar 1.

Bagaimanapun, jika objek masih digunakan di suatu tempat dalam kode, penghitung referensi akan lebih besar dari 0. Ketika nilai penghitung turun ke 0, fungsi khusus akan ikut bermain, yang "membebaskan" memori yang ditempati oleh objek. Memori ini kemudian dapat digunakan oleh objek lain.

Kami sekarang bertanya pada diri sendiri pertanyaan tentang apa "membebaskan memori" itu dan tentang bagaimana benda lain dapat menggunakan memori ini. Untuk menjawab pertanyaan ini, mari kita bicara tentang mekanisme manajemen memori di CPython.

Mekanisme Manajemen Memori dalam CPython


Sekarang kita akan berbicara tentang bagaimana CPython memiliki arsitektur memori dan bagaimana manajemen memori dilakukan di sana.

Seperti yang telah disebutkan, ada beberapa lapisan abstraksi antara CPython dan memori fisik. Sistem operasi mengabstraksi memori fisik dan membuat lapisan memori virtual yang dapat digunakan aplikasi (ini juga berlaku untuk Python).

Manajer memori virtual dari sistem operasi tertentu mengalokasikan sepotong memori untuk proses Python. Area abu-abu gelap pada gambar berikut adalah potongan memori yang termasuk dalam proses Python.


Area Memori Digunakan oleh CPython

Python menggunakan sejumlah memori untuk penggunaan internal dan untuk kebutuhan yang tidak terkait dengan mengalokasikan memori untuk objek. Sepotong memori lain digunakan untuk menyimpan objek (ini adalah nilai dari tipe int , dict , dan lain-lain seperti itu). Harap dicatat bahwa ini adalah diagram yang disederhanakan. Jika Anda ingin melihat gambar lengkapnya, lihat kode sumber CPython , di mana semua yang kita bicarakan sedang terjadi.

CPython memiliki fasilitas untuk mengalokasikan memori untuk objek, yang bertanggung jawab untuk mengalokasikan memori di area yang dimaksudkan untuk menyimpan objek. Hal yang paling menarik terjadi ketika mekanisme ini bekerja. Disebut ketika objek membutuhkan memori, atau dalam kasus di mana memori perlu dibebaskan.

Biasanya, menambahkan atau menghapus data ke objek Python seperti list dan int tidak melibatkan pemrosesan simultan jumlah informasi yang sangat besar. Oleh karena itu, arsitektur alat alokasi memori dibangun dengan mata pada pemrosesan sejumlah kecil data. Selain itu, alat ini berusaha untuk tidak mengalokasikan memori sampai menjadi jelas bahwa itu mutlak diperlukan.

Komentar dalam kode sumber menggambarkan alat alokasi memori sebagai "alat alokasi memori cepat, khusus untuk blok kecil yang dirancang untuk digunakan di atas malloc universal." Dalam hal ini, malloc adalah fungsi pustaka C yang dirancang untuk mengalokasikan memori.

Mari kita bahas strategi alokasi memori yang digunakan oleh CPython. Pertama, kita akan berbicara tentang tiga entitas - blok yang disebut (blok), kolam (pool) dan arena (arena), dan bagaimana mereka terkait satu sama lain.

Arena adalah fragmen memori terbesar. Mereka disejajarkan di batas halaman memori. Batas halaman adalah tempat blok terus menerus dari memori dengan panjang tetap berakhir digunakan oleh sistem operasi. Python, ketika bekerja dengan memori, mengasumsikan bahwa ukuran halaman memori sistem adalah 256 KB.


Arena, Kolam dan Blok

Kolam terletak di arena, yang merupakan 4 halaman memori virtual KB. Mereka menyerupai halaman-halaman buku dari contoh kita. Kolam dibagi menjadi blok memori kecil.

Semua blok di kolam yang sama milik kelas ukuran yang sama. Kelas ukuran tempat blok menentukan ukuran blok ini, yang dipilih dengan mempertimbangkan ukuran memori yang diminta. Berikut adalah tabel yang diambil dari kode sumber yang menunjukkan jumlah data yang diminta sistem untuk disimpan dalam memori, ukuran blok yang dialokasikan, dan pengidentifikasi kelas ukuran.
Jumlah data dalam byte
Ukuran blok
ukuran kelas idx
1-8
8
0
9-16
16
1
17-24
24
2
25-32
32
3
33-40
40
4
41-48
48
5
49-56
56
6
57-64
64
7
65-72
72
8
...
...
...
497-504
504
62
505-512
512
63

Misalnya, jika 42 byte diminta untuk disimpan, data akan ditempatkan di blok 48 byte.

Kolam renang


Kolam terdiri dari blok milik kelas ukuran yang sama. Setiap kumpulan dikaitkan dengan kumpulan lain yang berisi blok dengan kelas ukuran yang sama menggunakan mekanisme daftar tertaut ganda. Dengan pendekatan ini, algoritma alokasi memori dapat dengan mudah menemukan ruang kosong untuk satu blok ukuran tertentu, bahkan jika harus mencari ruang kosong di kumpulan yang berbeda.

Daftar usedpools memungkinkan Anda untuk melacak semua pool di mana ada ruang untuk data milik kelas ukuran tertentu. Ketika diminta untuk menyimpan blok ukuran tertentu, algoritma memeriksa daftar ini untuk daftar kumpulan yang menyimpan blok ukuran yang diperlukan.

Kolam itu sendiri harus di salah satu dari tiga negara. Yaitu, mereka dapat digunakan (negara used ), mereka dapat diisi ( full ) atau kosong ( empty ). Kolam yang digunakan memiliki blok gratis di mana dimungkinkan untuk menyimpan data dengan ukuran yang sesuai. Semua blok kumpulan diisi dialokasikan untuk data. Kumpulan kosong tidak berisi data, dan jika perlu, dapat ditugaskan untuk menyimpan blok milik kelas ukuran apa pun.

Daftar freepools menyimpan informasi tentang semua kumpulan yang dalam keadaan empty . Misalnya, jika tidak ada entri dalam daftar pool yang digunakan tentang kumpulan menyimpan blok ukuran 8 byte (kelas dengan idx 0), maka kumpulan baru diinisialisasi, yang dalam keadaan empty , yang dirancang untuk menyimpan blok tersebut. usedpools baru ini ditambahkan ke daftar usedpools , dapat digunakan untuk memenuhi permintaan untuk menyimpan data yang diterima setelah pembuatannya.

Misalkan dalam kumpulan yang dalam keadaan full , beberapa blok dibebaskan. Ini karena fakta bahwa data yang disimpan di dalamnya tidak lagi diperlukan. Pool ini lagi akan ada dalam daftar usedpools yang usedpools dan dapat digunakan untuk data dari kelas ukuran yang sesuai.

Pengetahuan tentang algoritma ini memungkinkan kita untuk memahami bagaimana keadaan kumpulan berubah selama operasi (dan bagaimana kelas ukuran berubah, blok milik yang dapat disimpan di dalamnya).

Blok



Kolam bekas, penuh dan kosong

Seperti yang dapat Anda lihat dari ilustrasi sebelumnya, pool berisi pointer ke blok "bebas" memori yang dikandungnya. Sehubungan dengan bekerja dengan blok, satu fitur kecil harus dicatat, yang ditunjukkan dalam kode sumber. Sistem manajemen memori yang digunakan dalam CPython, di semua tingkatan (arena, kolam, blok), berusaha untuk mengalokasikan memori hanya ketika itu benar-benar diperlukan.

Ini berarti bahwa kumpulan dapat berisi blok yang ada di salah satu dari tiga negara:

  • untouched adalah bagian dari memori yang belum dialokasikan.
  • free - bagian dari memori yang sudah dialokasikan, tetapi kemudian dibuat "gratis" oleh CPython dan tidak lagi berisi data berharga.
  • allocated adalah bagian dari memori yang berisi data berharga.

Pointer freeblock menunjuk ke daftar tunggal yang terhubung dari blok memori bebas. Dengan kata lain, ini adalah daftar tempat di mana Anda dapat menyimpan data. Jika lebih dari satu blok gratis diperlukan untuk menempatkan data, maka alat alokasi memori akan mengambil beberapa blok dari kumpulan yang tidak untouched .

Ketika alat manajemen memori membuat blok "bebas", mereka, ketika mereka memperoleh status free , sampai ke puncak daftar freeblock . Blok yang terkandung dalam daftar ini tidak selalu mewakili wilayah memori yang berdekatan seperti yang ditunjukkan pada gambar sebelumnya. Mereka mungkin benar-benar terlihat seperti di bawah ini.


Daftar freeblock tertaut tunggal

Arena


Arena berisi kolam. Kumpulan-kumpulan ini, seperti yang telah disebutkan, dapat berada di kondisi used , full atau empty . Perlu dicatat bahwa arena tidak memiliki status yang mirip dengan yang dimiliki oleh kumpulan.

Arena diatur ke dalam daftar yang ditautkan ganda yang disebut usable_arenas . Daftar ini diurutkan berdasarkan jumlah kolam gratis yang tersedia. Semakin sedikit kolam gratis di arena, semakin dekat arena ke bagian atas daftar.


Daftar usable_arenas

Ini berarti bahwa arena, yang lebih kuat dari yang lain diisi dengan data, akan dipilih untuk menempatkan data baru di dalamnya. Dan mengapa tidak sebaliknya? Mengapa tidak memposting data baru di arena dengan ruang paling bebas?

Faktanya, fitur ini menuntun kita pada gagasan untuk benar-benar membebaskan memori. Anda mungkin telah memperhatikan bahwa kita sering menggunakan konsep "membebaskan memori" di sini, melampirkannya dalam tanda kutip. Alasan mengapa ini dilakukan adalah bahwa meskipun blok dapat dianggap "bebas", potongan memori yang diwakilinya tidak benar-benar dikembalikan ke sistem operasi. Proses Python menyimpan bagian memori ini dan kemudian menggunakannya untuk menyimpan data baru. Membebaskan memori yang sebenarnya adalah kembali ke sistem operasinya, yang akan dapat menggunakannya.

Arena adalah satu-satunya entitas dalam skema yang dipertimbangkan di sini, memori yang diwakili olehnya dapat benar-benar dibebaskan. Akal sehat menyatakan bahwa skema kerja dengan arena yang dijelaskan di atas bertujuan untuk memungkinkan arena yang hampir kosong kosong sepenuhnya. Dengan pendekatan ini, potongan memori yang diwakili oleh arena yang benar-benar kosong dapat benar-benar dibebaskan, yang akan mengurangi jumlah memori yang dikonsumsi oleh Python.

Ringkasan


Inilah yang Anda pelajari dengan membaca materi ini:

  • Apa itu manajemen memori dan mengapa itu penting.
  • Bagaimana implementasi referensi Python, Cpython, ditulis dalam bahasa pemrograman C diatur.
  • Struktur data dan algoritma apa yang digunakan dalam CPython untuk manajemen memori.

Manajemen memori adalah bagian integral dari kerja program komputer. Python memecahkan hampir semua tugas manajemen memori tanpa disadari oleh programmer. Python memungkinkan siapa saja yang menulis dalam bahasa ini untuk mengabaikan banyak detail kecil yang terkait dengan bekerja dengan komputer. Ini memberi programmer kesempatan untuk bekerja di tingkat yang lebih tinggi, untuk membuat kode sendiri tanpa khawatir tentang di mana datanya disimpan.

Pembaca yang budiman! Jika Anda memiliki pengalaman dengan pengembangan Python, tolong beri tahu kami bagaimana Anda mendekati penggunaan memori dalam program Anda. Misalnya, apakah Anda berupaya menyimpannya?

Source: https://habr.com/ru/post/id441568/


All Articles