Eden Space
Jadi pertanyaan saya adalah apakah semua ini benar, dan jika demikian mengapa alokasi tumpukan java jauh lebih cepat.
Saya telah belajar sedikit tentang cara kerja Java GC karena sangat menarik bagi saya. Saya selalu mencoba untuk memperluas koleksi strategi alokasi memori saya di C dan C ++ (tertarik mencoba mengimplementasikan sesuatu yang serupa di C), dan itu adalah cara yang sangat, sangat cepat untuk mengalokasikan banyak objek secara burst mode dari sebuah perspektif praktis tetapi terutama karena multithreading.
Cara alokasi Java GC bekerja adalah dengan menggunakan strategi alokasi yang sangat murah untuk awalnya mengalokasikan objek ke ruang "Eden". Dari apa yang saya tahu, itu menggunakan pengalokasi kumpulan sekuensial.
Itu jauh lebih cepat hanya dalam hal algoritma dan mengurangi kesalahan halaman wajib daripada tujuan umum malloc
dalam C atau default, melempar operator new
dalam C ++.
Tapi pengalokasi sekuensial memiliki kelemahan mencolok: mereka dapat mengalokasikan potongan berukuran variabel, tetapi mereka tidak dapat membebaskan potongan individu. Mereka hanya mengalokasikan secara berurutan lurus dengan padding untuk perataan, dan hanya dapat membersihkan semua memori yang mereka dialokasikan sekaligus. Mereka biasanya berguna dalam C dan C ++ untuk membangun struktur data yang hanya membutuhkan penyisipan dan tanpa penghapusan elemen, seperti pohon pencarian yang hanya perlu dibangun sekali ketika program dimulai dan kemudian berulang kali dicari atau hanya memiliki kunci baru yang ditambahkan ( tidak ada kunci yang dihapus).
Mereka juga dapat digunakan bahkan untuk struktur data yang memungkinkan elemen untuk dihapus, tetapi elemen-elemen itu tidak benar-benar akan dibebaskan dari memori karena kita tidak dapat membatalkan alokasi mereka secara individual. Struktur seperti itu menggunakan pengalokasi sekuensial hanya akan mengkonsumsi lebih banyak dan lebih banyak memori, kecuali jika ada beberapa penundaan ditangguhkan di mana data disalin ke salinan yang baru, dipadatkan menggunakan pengalokasi sekuensial terpisah (dan itu kadang-kadang teknik yang sangat efektif jika pengalokasi tetap menang lakukan karena suatu alasan - hanya dengan lurus mengalokasikan salinan baru dari struktur data dan membuang semua memori yang lama).
Koleksi
Seperti pada contoh struktur data / kumpulan sekuensial di atas, itu akan menjadi masalah besar jika Java GC hanya mengalokasikan cara ini meskipun itu super cepat untuk alokasi burst banyak potongan individu. Itu tidak akan dapat membebaskan apa pun sampai perangkat lunak dimatikan, pada titik mana itu bisa membebaskan (membersihkan) semua kumpulan memori sekaligus.
Jadi, alih-alih, setelah siklus GC tunggal, pass dibuat melalui objek yang ada di ruang "Eden" (dialokasikan secara berurutan), dan yang masih direferensikan kemudian dialokasikan menggunakan pengalokasi yang lebih umum yang mampu membebaskan potongan individual. Orang-orang yang tidak lagi direferensikan akan dengan mudah dialokasikan dalam proses pembersihan. Jadi pada dasarnya itu "menyalin objek dari ruang Eden jika mereka masih dirujuk, dan kemudian membersihkan".
Ini biasanya akan cukup mahal, jadi itu dilakukan di utas latar belakang yang terpisah untuk menghindari secara signifikan menghentikan utas yang awalnya mengalokasikan semua memori.
Setelah memori disalin dari ruang Eden dan dialokasikan menggunakan skema yang lebih mahal ini yang dapat membebaskan potongan individu setelah siklus GC awal, objek bergerak ke wilayah memori yang lebih persisten. Potongan-potongan individual tersebut kemudian dibebaskan dalam siklus GC berikutnya jika tidak lagi menjadi referensi.
Kecepatan
Jadi, katakan dengan kasar, alasan Java GC mungkin mengungguli C atau C ++ pada alokasi heap langsung adalah karena menggunakan strategi alokasi termurah, yang sepenuhnya terdegenerasi di utas yang meminta untuk mengalokasikan memori. Maka menghemat pekerjaan yang lebih mahal yang biasanya perlu kita lakukan ketika menggunakan pengalokasi yang lebih umum seperti straight-up malloc
untuk utas lainnya.
Jadi secara konseptual GC sebenarnya harus melakukan lebih banyak pekerjaan secara keseluruhan, tetapi mendistribusikannya di seluruh utas sehingga biaya penuh tidak dibayar dimuka dengan satu utas. Hal ini memungkinkan thread mengalokasikan memori untuk melakukannya dengan sangat murah, dan kemudian menunda pengeluaran sebenarnya yang diperlukan untuk melakukan sesuatu dengan benar sehingga objek individu sebenarnya dapat dibebaskan ke utas lainnya. Dalam C atau C ++ ketika kami malloc
atau panggilan operator new
, kami harus membayar biaya penuh dimuka dalam utas yang sama.
Ini adalah perbedaan utama, dan mengapa Java mungkin mengungguli C atau C ++ dengan menggunakan panggilan naif ke malloc
atau operator new
untuk mengalokasikan sekelompok potongan kecil secara individual. Tentu saja biasanya akan ada beberapa operasi atom dan beberapa potensi penguncian ketika siklus GC dimulai, tetapi mungkin dioptimalkan sedikit.
Pada dasarnya penjelasan sederhana bermuara pada membayar biaya yang lebih berat dalam satu utas ( malloc
) vs. membayar biaya yang lebih murah dalam satu utas dan kemudian membayar biaya yang lebih berat di yang lain yang dapat berjalan secara paralel ( GC
). Sebagai downside melakukan hal-hal dengan cara ini menyiratkan bahwa Anda memerlukan dua tipuan untuk mendapatkan dari referensi objek ke objek yang diperlukan untuk memungkinkan pengalokasi untuk menyalin / memindahkan memori di sekitar tanpa membatalkan referensi objek yang ada, dan juga Anda dapat kehilangan spasial lokalitas setelah memori objek adalah pindah dari ruang "Eden".
Terakhir tetapi tidak kalah pentingnya, perbandingannya agak tidak adil karena kode C ++ biasanya tidak mengalokasikan muatan kapal secara individual pada heap. Kode C ++ yang layak cenderung mengalokasikan memori untuk banyak elemen di blok yang berdekatan atau di stack. Jika itu mengalokasikan muatan kapal benda-benda kecil satu per satu di toko gratis, kodenya shite.