Saya sangat suka manajemen memori berbasis ruang lingkup (SBMM), atau RAII , karena lebih umum (membingungkan?) Disebut oleh komunitas C ++. Sejauh yang saya tahu, kecuali untuk C ++ (dan C), tidak ada bahasa utama lain yang digunakan saat ini yang menjadikan SBMM / RAII mekanisme manajemen memori utama mereka, dan sebagai gantinya mereka lebih suka menggunakan pengumpulan sampah (GC).
Saya merasa ini agak membingungkan
- SBMM membuat program lebih deterministik (Anda bisa tahu persis kapan suatu objek dihancurkan);
- dalam bahasa yang menggunakan GC Anda sering harus melakukan manajemen sumber daya manual (lihat menutup file di Jawa, misalnya), yang sebagian mengalahkan tujuan GC dan juga rentan kesalahan;
- heap memory juga bisa (sangat elegan, imo) menjadi lingkup-terikat (lihat
std::shared_ptr
di C ++).
Mengapa SBMM tidak lebih banyak digunakan? Apa kerugiannya?
finalize()
metode objek akan dipanggil sebelum pengumpulan sampah. Akibatnya, ini menciptakan kelas masalah yang sama yang seharusnya dipecahkan oleh pengumpulan sampah.Jawaban:
Mari kita mulai dengan mendalilkan bahwa memori jauh (lusinan, ratusan atau bahkan ribuan waktu) lebih umum daripada semua sumber daya lainnya digabungkan. Setiap variabel tunggal, objek, anggota objek membutuhkan memori yang dialokasikan untuk itu dan dibebaskan nanti. Untuk setiap file yang Anda buka, Anda membuat lusinan hingga jutaan objek untuk menyimpan data yang ditarik dari file. Setiap aliran TCP berjalan bersama dengan sejumlah string byte sementara yang dibuat untuk ditulis ke aliran. Apakah kita ada di halaman yang sama di sini? Besar.
Agar RAII berfungsi (bahkan jika Anda memiliki smart pointer yang sudah jadi untuk setiap use case di bawah matahari), Anda perlu mendapatkan hak kepemilikan . Anda perlu menganalisis siapa yang harus memiliki objek ini atau itu, siapa yang tidak boleh, dan kapan kepemilikan harus ditransfer dari A ke B. Tentu, Anda bisa menggunakan kepemilikan bersama untuk semuanya , tetapi kemudian Anda akan meniru GC melalui pointer pintar. Pada saat itu menjadi lebih mudah dan lebih cepat untuk membangun GC ke dalam bahasa.
Pengumpulan sampah membebaskan Anda dari kekhawatiran ini akan sumber daya yang paling umum digunakan, memori. Tentu, Anda masih perlu membuat keputusan yang sama untuk sumber daya lain, tetapi itu jauh kurang umum (lihat di atas), dan kepemilikan yang rumit (misalnya dibagi) juga kurang umum. Beban mental berkurang secara signifikan.
Sekarang, Anda menyebutkan beberapa kerugian untuk membuat semua nilai sampah dikumpulkan. Namun, mengintegrasikan GC yang aman memori dan tipe nilai dengan RAII ke dalam satu bahasa sangat sulit, jadi mungkin lebih baik untuk memigrasikan trade off ini melalui cara lain?
Hilangnya determinisme ternyata tidak terlalu buruk dalam praktik, karena hanya mempengaruhi seumur hidup objek deterministik . Seperti dijelaskan dalam paragraf berikutnya, sebagian besar sumber daya (selain dari ingatan, yang berlimpah dan dapat didaur ulang dengan agak malas) tidak terikat dengan objek seumur hidup dalam bahasa-bahasa ini. Ada beberapa kasus penggunaan lainnya, tetapi jarang dalam pengalaman saya.
Poin kedua Anda, manajemen sumber daya manual, saat ini ditangani melalui pernyataan yang melakukan pembersihan berbasis ruang lingkup, tetapi tidak memasangkan pembersihan ini dengan waktu hidup objek (karenanya tidak berinteraksi dengan GC dan keamanan memori). Ini
using
dalam C #,with
dalam Python,try
-dengan-sumber daya dalam versi Java terbaru.sumber
using
pernyataan hanya dimungkinkan secara lokal. Tidak mungkin untuk membersihkan sumber daya yang disimpan dalam variabel anggota dengan cara itu.using
adalah lelucon dibandingkan dengan RAII, asal tahu saja.RAII juga mengikuti dari manajemen memori penghitungan referensi otomatis, misalnya seperti yang digunakan oleh Perl. Walaupun penghitungan referensi mudah diterapkan, deterministik, dan cukup berkinerja, ia tidak dapat berurusan dengan referensi melingkar (menyebabkan kebocoran) yang mengapa itu tidak umum digunakan.
Bahasa yang dikumpulkan sampah tidak dapat menggunakan RAII secara langsung, tetapi sering menawarkan sintaksis dengan efek yang setara. Di Jawa, kami memiliki pernyataan coba-dengan-sumber daya
yang secara otomatis memanggil
.close()
sumber daya di blok keluar. C # memilikiIDisposable
antarmuka, yang memungkinkan.Dispose()
untuk dipanggil ketika meninggalkanusing (...) { ... }
pernyataan. Python memilikiwith
pernyataan:yang bekerja dengan cara yang sama. Dalam putaran menarik tentang ini, metode buka file Ruby mendapat panggilan balik. Setelah panggilan balik dilakukan, file ditutup.
Saya pikir Node.js menggunakan strategi yang sama.
sumber
with-open-filehandle
fungsi yang membuka file, menghasilkannya ke fungsi dan setelah kembali fungsi tutup file lagi.Menurut pendapat saya, keuntungan pengumpulan sampah yang paling meyakinkan adalah memungkinkan kompabilitas . Kebenaran manajemen memori adalah properti lokal di lingkungan pengumpulan sampah. Anda dapat melihat setiap bagian secara terpisah dan menentukan apakah dapat membocorkan memori. Gabungkan sejumlah bagian yang benar dengan memori dan tetaplah benar.
Ketika Anda mengandalkan penghitungan referensi, Anda kehilangan properti itu. Apakah aplikasi Anda dapat membocorkan memori menjadi properti global seluruh aplikasi dengan penghitungan referensi. Setiap interaksi baru antar bagian memiliki kemungkinan untuk menggunakan kepemilikan yang salah dan merusak manajemen memori.
Ini memiliki efek yang sangat terlihat pada desain program dalam berbagai bahasa. Program dalam bahasa-bahasa GC cenderung menjadi sup yang lebih banyak dari objek-objek dengan banyak interaksi, sedangkan dalam bahasa-bahasa yang kurang GC seseorang cenderung lebih suka bagian-bagian yang terstruktur dengan interaksi yang terkontrol secara ketat dan terbatas di antara mereka.
sumber
Penutupan adalah fitur penting dari hampir semua bahasa modern. Mereka sangat mudah diimplementasikan dengan GC dan sangat sulit (meskipun bukan tidak mungkin) untuk mendapatkan yang benar dengan RAII, karena salah satu fitur utama mereka adalah mereka memungkinkan Anda untuk abstrak selama masa hidup variabel Anda!
C ++ hanya mendapatkan mereka 40 tahun setelah semua orang melakukannya, dan butuh banyak kerja keras oleh banyak orang pintar untuk memperbaikinya. Sebaliknya, banyak bahasa scripting yang dirancang dan diimplementasikan oleh orang-orang yang tidak memiliki pengetahuan dalam mendesain dan mengimplementasikan bahasa pemrograman memilikinya.
sumber
[&]
sintaks itu. Setiap programmer C ++ sudah mengaitkan&
tanda dengan referensi dan tahu tentang referensi basi.Bagi sebagian besar programmer, OS bersifat non-deterministik, pengalokasi memori mereka adalah non-deterministik dan sebagian besar program yang mereka tulis bersamaan dan, karenanya, pada dasarnya non-deterministik. Menambahkan kendala bahwa destruktor disebut tepat di akhir lingkup daripada sedikit sebelum atau sedikit setelah itu bukan manfaat praktis yang signifikan bagi sebagian besar programmer.
Lihat
using
di C # danuse
di F #.Dengan kata lain, Anda dapat mengambil tumpukan yang merupakan solusi tujuan umum dan mengubahnya menjadi hanya berfungsi dalam kasus tertentu yang sangat membatasi. Itu benar, tentu saja, tetapi tidak berguna.
SBMM membatasi apa yang dapat Anda lakukan:
SBMM menciptakan masalah funarg ke atas dengan penutupan leksikal kelas satu yang mengapa penutupan populer dan mudah digunakan dalam bahasa seperti C # tetapi jarang dan rumit dalam C ++. Perhatikan bahwa ada kecenderungan umum terhadap penggunaan konstruksi fungsional dalam pemrograman.
SBMM membutuhkan destruktor dan mereka menghalangi panggilan ekor dengan menambahkan lebih banyak pekerjaan yang harus dilakukan sebelum fungsi dapat kembali. Panggilan ekor berguna untuk mesin negara yang dapat diperpanjang dan disediakan oleh hal-hal seperti .NET.
Beberapa struktur data dan algoritma terkenal sulit diimplementasikan menggunakan SBMM. Pada dasarnya di mana saja siklus itu terjadi secara alami. Terutama algoritma grafik. Anda secara efektif akhirnya menulis GC Anda sendiri.
Pemrograman bersamaan lebih sulit karena aliran kontrol dan, oleh karena itu, umur objek secara inheren non-deterministik di sini. Solusi praktis dalam sistem penyampaian pesan cenderung berupa penyalinan pesan yang mendalam dan penggunaan masa hidup yang terlalu lama.
SBMM membuat objek tetap hidup sampai akhir ruang lingkupnya dalam kode sumber yang seringkali lebih lama dari yang diperlukan dan bisa jauh lebih lama dari yang diperlukan. Ini meningkatkan jumlah sampah terapung (objek yang tidak terjangkau menunggu untuk didaur ulang). Sebaliknya, melacak pengumpulan sampah cenderung membebaskan benda segera setelah referensi terakhir kepada mereka menghilang yang bisa lebih cepat. Lihat mitos manajemen memori: ketepatan waktu .
SBMM sangat terbatas sehingga programmer membutuhkan jalan keluar untuk situasi di mana masa hidup tidak dapat dibuat untuk bersarang. Di C ++,
shared_ptr
menawarkan rute pelarian tetapi bisa ~ 10x lebih lambat daripada melacak pengumpulan sampah . Jadi menggunakan SBMM dan bukan GC akan membuat kebanyakan orang salah menginjak sebagian besar waktu. Itu tidak berarti, bahwa itu tidak berguna. SBMM masih bernilai dalam konteks sistem dan pemrograman tertanam di mana sumber daya terbatas.FWIW Anda mungkin ingin memeriksa Forth dan Ada, dan membaca karya Nicolas Wirth.
sumber
shared_ptr
jarang ada di C ++ karena sangat lambat. Kedua, itu adalah perbandingan apel dan jeruk (seperti yang telah saya sebutkan di artikel yang saya sebutkan) karenashared_ptr
berkali-kali lebih lambat daripada GC produksi. Ketiga, GC tidak ada di mana-mana dan dihindari dalam perangkat lunak seperti LMax dan mesin FIX Penambahan Cepat.Melihat beberapa indeks popularitas seperti TIOBE (yang tentu saja dapat diperdebatkan, tapi saya kira untuk jenis pertanyaan Anda tidak masalah untuk menggunakan ini), Anda pertama kali melihat bahwa ~ 50% dari 20 teratas adalah "bahasa scripting" atau "dialek SQL" ", di mana" kemudahan penggunaan "dan sarana abstraksi memiliki jauh lebih penting daripada perilaku deterministik. Dari sisa bahasa "dikompilasi", ada sekitar 50% dari bahasa dengan SBMM dan ~ 50% tanpa. Jadi ketika mengeluarkan bahasa scripting dari perhitungan Anda, saya akan mengatakan anggapan Anda salah, di antara bahasa yang dikompilasi, yang dengan SBMM sama populernya dengan yang tidak.
sumber
Satu keuntungan utama dari sistem GC yang belum pernah disebutkan siapa pun adalah bahwa referensi dalam sistem GC dijamin untuk mempertahankan identitasnya selama ada . Jika seseorang memanggil
IDisposable.Dispose
(.NET) atauAutoCloseable.Close
(Java) pada suatu objek sementara salinan referensi ada, salinan itu akan terus merujuk ke objek yang sama. Objek tidak akan berguna untuk apa pun lagi, tetapi upaya untuk menggunakannya akan memiliki perilaku yang dapat diprediksi dikendalikan oleh objek itu sendiri. Sebaliknya, dalam C ++, jika kode memanggildelete
suatu objek dan kemudian mencoba menggunakannya, seluruh kondisi sistem menjadi benar-benar tidak terdefinisi.Hal penting lain yang perlu diperhatikan adalah manajemen memori berbasis lingkup bekerja sangat baik untuk objek dengan kepemilikan yang jelas. Ini bekerja kurang baik, dan kadang-kadang benar-benar buruk, dengan benda-benda yang tidak memiliki kepemilikan yang jelas. Secara umum, objek yang dapat berubah harus memiliki pemilik, sedangkan objek yang tidak dapat diubah tidak perlu, tetapi ada kerutan: sangat umum untuk kode menggunakan instance dari tipe yang bisa berubah untuk menyimpan data yang tidak dapat diubah, dengan memastikan bahwa tidak ada referensi yang akan terpapar ke kode yang mungkin mengubah contoh. Dalam skenario seperti itu, instance dari kelas yang dapat berubah mungkin dibagi di antara beberapa objek yang tidak dapat diubah, dan dengan demikian tidak memiliki kepemilikan yang jelas.
sumber
Pertama, sangat penting untuk menyadari bahwa menyamakan RAII dengan SBMM. atau bahkan ke SBRM. Salah satu kualitas RAII yang paling esensial (dan paling tidak diketahui atau paling tidak dihargai) adalah kenyataan bahwa RAII menjadikan 'menjadi sumber daya' properti yang TIDAK transitif terhadap komposisi.
Posting blog berikut membahas aspek penting RAII ini dan membandingkannya dengan perubahan sumber daya dalam bahasa GCed yang menggunakan GC non-deterministik.
http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/
Penting untuk dicatat bahwa sementara RAII sebagian besar digunakan dalam C ++, Python (akhirnya versi non-VM) memiliki destruktor dan deterministik GC yang memungkinkan RAII untuk digunakan bersama dengan GC. Terbaik dari kedua dunia itu.
sumber
File.ReadLines file |> Seq.length
mana abstraksi menangani penutupan untuk saya. Kunci dan utas saya telah diganti dengan .NETTask
dan F #MailboxProcessor
. Seluruh ini "Kami meledak jumlah manajemen sumber daya manual" hanya omong kosong.