Saya sudah memposting pertanyaan ini pada SO dan itu tidak masalah. Sayangnya itu ditutup (hanya perlu satu suara untuk membuka kembali) tetapi seseorang menyarankan saya mempostingnya di sini karena lebih cocok sehingga berikut ini secara harfiah merupakan salinan dari pertanyaan.
Saya membaca komentar tentang jawaban ini dan saya melihat kutipan ini.
Instansiasi objek dan fitur berorientasi objek sangat cepat digunakan (lebih cepat daripada C ++ dalam banyak kasus) karena mereka dirancang sejak awal. dan Koleksi cepat. Java standar mengalahkan C / C ++ standar di area ini, bahkan untuk kode C paling optimal.
Satu pengguna (dengan rep yang sangat tinggi, saya dapat menambahkan) dengan berani membela klaim ini, menyatakan itu
alokasi heap di java lebih baik daripada C ++
dan menambahkan pernyataan ini membela koleksi di java
Dan koleksi Java lebih cepat dibandingkan dengan koleksi C ++ karena sebagian besar subsistem memori yang berbeda.
Jadi pertanyaan saya adalah apakah semua ini benar, dan jika demikian mengapa alokasi tumpukan java jauh lebih cepat.
sumber
Jawaban:
Ini adalah pertanyaan yang menarik, dan jawabannya rumit.
Secara keseluruhan, saya pikir adil untuk mengatakan bahwa pemulung sampah JVM dirancang dengan sangat baik dan sangat efisien. Ini mungkin sistem manajemen memori tujuan umum terbaik .
C ++ dapat mengalahkan JVM GC dengan pengalokasi memori khusus yang dirancang untuk tujuan tertentu. Contohnya mungkin:
Pengalokasi memori khusus, tentu saja, dibatasi oleh definisi. Mereka biasanya memiliki batasan pada siklus hidup objek dan / atau batasan pada jenis objek yang dapat dikelola. Pengumpulan sampah jauh lebih fleksibel.
Pengumpulan sampah juga memberi Anda beberapa keuntungan signifikan dari perspektif kinerja:
Java GC memiliki satu kelemahan utama: karena pekerjaan mengumpulkan sampah ditangguhkan dan dilakukan dalam potongan-potongan pekerjaan secara berkala, itu menyebabkan GC sesekali berhenti mengumpulkan sampah, yang dapat memengaruhi latensi. Ini biasanya bukan masalah untuk aplikasi tipikal, tetapi dapat mengesampingkan Java dalam situasi di mana hard realtime merupakan persyaratan (misalnya kontrol robot). Soft realtime (mis. Game, multimedia) biasanya OK.
sumber
Ini bukan klaim ilmiah. Saya hanya memberikan beberapa bahan untuk dipikirkan tentang masalah ini.
Satu analogi visual adalah ini: Anda diberi apartemen (unit perumahan) yang berkarpet. Karpetnya kotor. Apa cara tercepat (dalam hal jam) untuk membuat lantai apartemen berkilau bersih?
Jawaban: cukup gulung karpet lama; membuang; dan gulung karpet baru.
Apa yang kita abaikan di sini?
Pengumpulan sampah adalah topik besar dan ada banyak pertanyaan baik di Programmers.SE dan StackOverflow.
Pada masalah sampingan, manajer alokasi C / C ++ yang dikenal sebagai TCMalloc bersama-sama dengan penghitungan referensi objek secara teoritis dapat memenuhi klaim kinerja terbaik dari setiap sistem GC.
sumber
Alasan utama adalah bahwa, ketika Anda meminta Jawa untuk benjolan memori baru, itu langsung menuju ke ujung tumpukan dan memberi Anda sebuah blok. Dengan cara ini, alokasi memori secepat mengalokasikan pada stack (yang merupakan cara Anda melakukannya sebagian besar waktu di C / C ++, tetapi terlepas dari itu ..)
Jadi alokasi cepat seperti apa pun tetapi ... itu tidak termasuk biaya membebaskan memori. Hanya karena Anda tidak membebaskan apa pun sampai nanti tidak berarti itu tidak memerlukan biaya yang cukup besar, dan dalam kasus sistem GC, biayanya jauh lebih banyak daripada alokasi tumpukan 'normal' - tidak hanya GC harus menjalankan semua objek untuk melihat apakah mereka masih hidup atau tidak, itu juga kemudian harus membebaskan mereka, dan (biaya besar) menyalin memori sekitar untuk memadatkan tumpukan - sehingga Anda dapat memiliki alokasi cepat di akhir mekanisme (atau Anda kehabisan memori, C / C ++ misalnya akan berjalan tumpukan pada setiap alokasi mencari blok ruang kosong berikutnya yang dapat sesuai dengan objek).
Ini adalah salah satu alasan mengapa tolok ukur Java / .NET menunjukkan kinerja yang sangat baik, namun aplikasi dunia nyata menunjukkan kinerja yang sangat buruk. Saya hanya perlu melihat aplikasi di ponsel saya - yang sangat cepat, responsif semuanya ditulis menggunakan NDK, bahkan saya sangat terkejut.
Koleksi saat ini dapat cepat jika semua objek dialokasikan secara lokal, misalnya dalam satu blok yang berdekatan. Sekarang, di Jawa, Anda tidak mendapatkan blok yang berdekatan karena objek dialokasikan satu per satu dari ujung bebas heap. Anda dapat berakhir dengan mereka berdekatan dengan senang hati, tetapi hanya dengan keberuntungan (yaitu sampai ke kemauan rutinitas pemadatan GC dan bagaimana ia menyalin objek). C / C ++ di sisi lain secara eksplisit mendukung alokasi yang berdekatan (via stack, jelas). Secara umum tumpukan objek dalam C / C ++ tidak berbeda dengan BTW Java.
Sekarang dengan C / C ++ Anda bisa menjadi lebih baik daripada pengalokasi default yang dirancang untuk menghemat memori dan menggunakannya secara efisien. Anda dapat mengganti pengalokasi dengan kumpulan kumpulan blok tetap, sehingga Anda selalu dapat menemukan blok yang persis berukuran tepat untuk objek yang Anda alokasikan. Berjalan tumpukan hanya menjadi masalah pencarian bitmap untuk melihat di mana blok gratis berada, dan de-alokasi hanya mengatur ulang sedikit dalam bitmap itu. Biayanya adalah Anda menggunakan lebih banyak memori saat Anda mengalokasikan dalam blok ukuran tetap, sehingga Anda memiliki tumpukan blok 4 byte, yang lain untuk blok 16 byte, dll.
sumber
Eden Space
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, melemparoperator 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 panggilanoperator 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
atauoperator 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.
sumber
Itu semua tergantung siapa yang mengukur kecepatan, kecepatan implementasi apa yang mereka ukur, dan apa yang ingin mereka buktikan. Dan apa yang mereka bandingkan.
Jika Anda hanya melihat alokasi / deallocating, di C ++ Anda mungkin memiliki 1.000.000 panggilan ke malloc, dan 1.000.000 panggilan gratis (). Di Jawa, Anda akan memiliki 1.000.000 panggilan ke objek baru () dan seorang pengumpul sampah berjalan dalam satu lingkaran menemukan 1.000.000 objek yang dapat dibebaskan. Pengulangan dapat lebih cepat daripada panggilan gratis ().
Di sisi lain, malloc / free telah meningkatkan waktu lainnya, dan biasanya malloc / free hanya menetapkan satu bit dalam struktur data yang terpisah, dan dioptimalkan untuk malloc / free terjadi di utas yang sama, sehingga dalam lingkungan multithreaded tidak ada variabel memori bersama digunakan dalam banyak kasus (dan variabel penguncian atau memori bersama sangat mahal).
Di pihak ketiga, ada hal-hal seperti penghitungan referensi yang mungkin Anda perlukan tanpa pengumpulan sampah, dan itu tidak gratis.
sumber