Latar Belakang
Saya mengunjungi kembali situs lama (tapi hebat) yang sudah lama tidak saya kunjungi - Penembakan Bahasa Alioth ( http://benchmarksgame.alioth.debian.org/ ).
Saya memulai pemrograman dalam C / C ++ beberapa tahun yang lalu, tetapi sejak itu telah bekerja hampir secara eksklusif di Jawa karena kendala bahasa dalam proyek-proyek yang telah saya ikuti. Tidak ingat angka-angkanya, saya ingin melihat, kira-kira, seberapa baik Jawa bernasib melawan C / C ++ dalam hal penggunaan sumber daya.
Waktu eksekusi masih relatif baik, dengan Java paling buruk berkinerja 4x lebih lambat dari C / C ++, tetapi rata-rata sekitar (atau di bawah) 2x. Karena sifat implementasi Java itu sendiri, ini tidak mengejutkan, dan waktu kinerja sebenarnya lebih rendah dari yang saya harapkan.
Batu bata yang sebenarnya adalah alokasi memori - paling buruk, Jawa dialokasikan:
- memori kekalahan 52x lebih banyak dari C
- dan 25x lebih banyak dari C ++.
52x memori ... Benar-benar jahat, bukan? ... atau itu? Memori sekarang relatif murah.
Pertanyaan:
Jika kita tidak berbicara dalam hal platform target dengan batasan ketat pada memori kerja (yaitu sistem tertanam dan sejenisnya), haruskah penggunaan memori menjadi perhatian saat memilih bahasa tujuan umum hari ini?
Saya bertanya sebagian karena saya mempertimbangkan migrasi ke Scala sebagai bahasa utama saya. Saya sangat menyukai aspek fungsionalnya, tetapi dari apa yang saya lihat itu bahkan lebih mahal dalam hal memori daripada Java. Namun, karena memori tampaknya semakin cepat, lebih murah, dan semakin banyak dari tahun ke tahun (tampaknya semakin sulit untuk menemukan laptop konsumen tanpa setidaknya 4GB DDR3 RAM), tidak dapat dibantah bahwa manajemen sumber daya menjadi semakin lebih tidak relevan dibandingkan dengan fitur-fitur bahasa tingkat tinggi (yang mungkin mahal untuk implementasi) yang memungkinkan pembangunan lebih cepat dari solusi yang lebih mudah dibaca?
Jawaban:
Manajemen memori sangat relevan karena mengatur seberapa cepat sesuatu muncul bahkan jika sesuatu itu memiliki banyak memori. Contoh terbaik dan paling kanonik adalah game judul AAA seperti Call of Duty atau Bioshock. Ini adalah aplikasi real-time efektif yang membutuhkan sejumlah besar kontrol dalam hal optimasi dan penggunaan. Bukan penggunaan per se yang menjadi masalah melainkan manajemen.
Itu datang ke dua kata: Pengumpulan Sampah. Algoritma Pengumpulan Sampah dapat menyebabkan sedikit gangguan dalam kinerja atau bahkan menyebabkan aplikasi hang untuk satu atau dua detik. Sebagian besar tidak berbahaya dalam aplikasi akuntansi tetapi berpotensi merusak dalam hal pengalaman pengguna dalam permainan Call of Duty. Jadi dalam aplikasi di mana masalah waktu, sampah yang dikumpulkan bahasa bisa sangat bermasalah. Ini adalah salah satu tujuan desain Squirrel misalnya, yang berupaya memperbaiki masalah yang Lua miliki dengan GC-nya dengan menggunakan penghitungan referensi sebagai gantinya.
Apakah itu lebih dari sakit kepala? Tentu tetapi jika Anda membutuhkan kontrol yang tepat, Anda tahan dengan itu.
sumber
Apakah Anda memahami angka-angka yang menjadi dasar pertanyaan Anda?
Ketika ada perbedaan besar antara program-program Java dan C, itu sebagian besar alokasi memori JVM default versus apa pun kebutuhan libc:
program Java n-body 13.996KB :: program C 320KB :: Pascal 8KB gratis
Lihatlah tugas-tugas yang memang membutuhkan memori untuk dialokasikan (atau menggunakan buffer tambahan untuk mengumpulkan hasil dari program multicore):
mandelbrot
Java program 67 , 880KB :: C program 30 , 444KB
k-nukleotida
Program Java 494 , 040KB :: program C 153 , 452KB
program Java reverse-pelengkap 511 , 484KB :: C program 248 , 632KB
regex-dna
program Java 557 , 080KB :: program C 289 , 088KB
binary-tree
Program Java 506 , 592KB :: C program 99 , 448KB
Itu tergantung apakah penggunaan spesifik , untuk pendekatan spesifik Anda untuk memecahkan masalah spesifik yang perlu Anda selesaikan, akan dibatasi oleh batas spesifik memori yang tersedia pada platform spesifik yang akan digunakan.
sumber
Seperti halnya semua hal, itu adalah trade-off.
Jika Anda sedang membangun aplikasi yang akan berjalan pada desktop pengguna tunggal dan dapat diharapkan untuk mengontrol sebagian besar RAM pada mesin itu, mungkin ada baiknya mengorbankan penggunaan memori untuk kecepatan implementasi. Jika Anda menargetkan mesin yang sama tetapi Anda sedang membangun sebuah utilitas kecil yang akan bersaing dengan banyak aplikasi lain yang haus memori yang berjalan secara bersamaan, Anda mungkin ingin lebih berhati-hati tentang pertukaran tersebut. Seorang pengguna mungkin baik-baik saja dengan permainan yang menginginkan semua ingatan mereka ketika sedang berjalan (meskipun, seperti yang ditunjukkan oleh World Engineer, mereka Saya akan khawatir jika pengumpul sampah memutuskan untuk menghentikan sementara tindakan secara berkala untuk melakukan sweep) - mereka cenderung kurang antusias jika pemutar musik yang mereka jalankan di latar belakang saat melakukan hal-hal lain memutuskan untuk melahap satu ton memori dan mengganggu kemampuan mereka untuk bekerja. Jika Anda membuat aplikasi berbasis web, setiap memori yang Anda gunakan di server membatasi kemampuan Anda untuk memaksakan diri untuk mengeluarkan lebih banyak uang pada server aplikasi yang lebih banyak untuk mendukung kelompok pengguna yang sama. Itu dapat memiliki dampak besar pada ekonomi perusahaan sehingga Anda mungkin ingin sangat berhati-hati dalam melakukan pertukaran. memori apa pun yang Anda gunakan di server membatasi kemampuan Anda untuk memaksa Anda mengeluarkan lebih banyak uang pada server aplikasi lebih banyak untuk mendukung kelompok pengguna yang sama. Itu dapat memiliki dampak besar pada ekonomi perusahaan sehingga Anda mungkin ingin sangat berhati-hati dalam melakukan pertukaran. memori apa pun yang Anda gunakan di server membatasi kemampuan Anda untuk memaksa Anda mengeluarkan lebih banyak uang pada server aplikasi lebih banyak untuk mendukung kelompok pengguna yang sama. Itu dapat memiliki dampak besar pada ekonomi perusahaan sehingga Anda mungkin ingin sangat berhati-hati dalam melakukan pertukaran.
sumber
Itu tergantung pada sejumlah faktor, terutama skala di mana Anda bekerja.
Demi argumen, mari kita asumsikan perbedaan 30x dalam memori dan 2x dalam penggunaan CPU.
Jika Anda berurusan dengan program interaktif yang akan mengambil 10 megabyte memori dan 1 milidetik CPU jika ditulis dalam C, itu sangat tidak penting - 300 megabita memori dan 2 milidetik untuk dieksekusi biasanya sama sekali tidak relevan pada desktop yang khas, dan tidak mungkin berarti banyak bahkan di ponsel atau tablet.
Perbedaan antara membutuhkan sekitar setengah sumber daya dari 1 server dan membutuhkan 15 server adalah langkah yang jauh lebih besar - terutama karena scaling out ke 15 server cenderung membutuhkan banyak pekerjaan ekstra untuk dikembangkan, bukan lebih sedikit. Sejauh ekspansi masa depan berjalan, faktor-faktor yang sama yang Anda sebutkan cenderung menunjukkan bahwa kecuali jika basis pelanggan Anda mengalami pertumbuhan besar-besaran , bahwa jika itu akan berjalan pada satu server sekarang, kemungkinan cukup bagus bahwa ketika Anda melebihi server itu, Anda akan menjadi dapat menggantinya dengan satu server yang lebih baru tanpa masalah.
Faktor lain yang benar-benar perlu Anda pertimbangkan adalah seberapa besar perbedaan dalam biaya pengembangan yang akan Anda lihat untuk tugas khusus Anda. Saat ini, Anda pada dasarnya melihat satu sisi persamaan. Untuk mendapatkan gambaran yang baik tentang biaya vs manfaat, Anda (jelas cukup) perlu melihat biaya dan manfaat, bukan hanya satu saja. Pertanyaan sebenarnya adalah: "apakah x lebih besar dari y?" - tetapi Anda tidak dapat menentukannya hanya dengan melihat x. Anda jelas perlu melihat y juga.
sumber
Manajemen memori sangat relevan di dunia saat ini. Namun, tidak dengan cara yang Anda harapkan. Bahkan dalam bahasa sampah yang dikumpulkan Anda harus memastikan Anda tidak memiliki kebocoran referensi
Anda melakukan sesuatu yang salah jika ini kode Anda:
Pengumpulan sampah tidak dapat secara ajaib tahu Anda tidak akan pernah menggunakan referensi lagi kecuali jika Anda membuatnya sehingga Anda tidak dapat menggunakannya lagi, yaitu dengan melakukan
Cache=null
, Anda secara efektif mengingatkan pengumpul sampah bahwa "hei saya tidak akan dapat akses lagi. Lakukan apa yang Anda inginkan dengan itu "Ini lebih rumit dari itu, tetapi kebocoran referensi sama, jika tidak lebih, berbahaya daripada kebocoran memori tradisional.
Ada juga beberapa tempat di mana Anda tidak dapat memasukkan pengumpul sampah. Sebagai contoh, ATTiny84 adalah mikrokontroler dengan ROM kode 512 byte, dan RAM 32 byte. Semoga berhasil! Itu ekstrem, dan mungkin tidak akan diprogram dalam apa pun selain perakitan, tapi tetap saja. Kasing lainnya, Anda mungkin memiliki 1M memori. Tentu, Anda dapat memasukkan pengumpul sampah, tetapi jika prosesornya sangat lambat (baik karena keterbatasan, atau untuk menghemat baterai), maka Anda tidak akan ingin menggunakan pengumpul sampah karena terlalu mahal untuk melacak apa yang bisa diketahui oleh seorang programmer. .
Mengumpulkan sampah juga jauh lebih sulit saat Anda membutuhkan waktu respons yang terjamin. Seperti, jika Anda memiliki monitor jantung atau sesuatu dan ketika menerima
1
pada beberapa port, Anda perlu memastikan Anda dapat menanggapinya dengan sinyal yang tepat atau sesuatu dalam jarak 10 ms. Jika di tengah rutinitas respons Anda, pemulung perlu membuat izin dan akhirnya membutuhkan 100 ms untuk merespons, itu mungkin seseorang yang meninggal. Pengumpulan sampah sangat sulit, jika bukan tidak mungkin, untuk digunakan ketika persyaratan waktu perlu dijamin.Dan tentu saja, bahkan pada perangkat keras modern, ada beberapa kasus di mana Anda membutuhkan kinerja tambahan 2% dengan tidak khawatir tentang overhead dari pengumpul sampah.
sumber
Seperti yang dikatakan Donald Knuth, optimisasi prematur adalah akar dari semua kejahatan. Kecuali Anda memiliki alasan untuk percaya bahwa ingatan akan menjadi hambatan, jangan khawatir tentang hal itu. Dan mengingat bahwa hukum Moore masih memberikan peningkatan kapasitas memori (meskipun kita tidak mendapatkan kode single-threaded lebih cepat dari itu), ada setiap alasan untuk percaya bahwa di masa depan kita akan lebih dibatasi pada memori daripada kita hari ini.
Yang mengatakan, jika optimasi tidak prematur, tentu saja lakukanlah. Saya secara pribadi sedang mengerjakan sebuah proyek saat ini di mana saya memahami penggunaan memori saya dengan sangat rinci, saya benar-benar membutuhkan kontrol yang tepat, dan pembersihan sampah akan membunuh saya. Karena itu saya melakukan proyek ini dalam C ++. Tetapi pilihan itu tampaknya menjadi acara beberapa tahun sekali bagi saya. (Semoga dalam beberapa minggu saya tidak akan menyentuh C ++ lagi selama beberapa tahun lagi.)
sumber
Bagi orang yang berurusan dengan "big data" manajemen memori masih merupakan masalah besar. Program dalam astronomi, fisika, bioinformatika, pembelajaran mesin, dll., Semua harus berurusan dengan set data multi-gigabyte, dan program berjalan jauh lebih cepat jika bagian yang relevan dapat disimpan dalam memori. Bahkan menjalankan mesin dengan RAM 128GB tidak menyelesaikan masalah.
Ada juga masalah memanfaatkan GPU, meskipun mungkin Anda akan mengklasifikasikannya sebagai sistem tertanam. Sebagian besar pemikiran sulit dalam menggunakan CUDA atau OpenCL bermuara pada masalah manajemen memori dalam mentransfer data dari memori utama ke memori GPU.
sumber
Agar adil, banyak orang Jawa di luar sana yang menikmati pola ledakan kelas yang benar-benar dan tanpa tujuan yang hanya membunuh kinerja dan memori babi, tetapi saya bertanya-tanya berapa banyak dari memori itu hanyalah JVM yang secara teori (heh) mari kita jalankan aplikasi yang sama di berbagai lingkungan tanpa harus sepenuhnya menulis ulang yang baru. Oleh karena itu pertanyaan tradeoff desain semuanya bermuara pada: "Berapa banyak memori pengguna Anda yang merupakan keuntungan pengembangan yang berharga bagi Anda?"
Ini, IMO merupakan tradeoff yang sangat berharga dan masuk akal untuk dipertimbangkan. Apa yang membuat saya kesal adalah gagasan bahwa karena PC modern begitu kuat dan ingatannya sangat murah, kita dapat sepenuhnya mengabaikan masalah dan fitur yang menggembung seperti itu dan kode yang menggembung dan menjadi malas tentang pilihan ke titik di mana sepertinya banyak hal Saya lakukan pada PC windows sekarang, hanya membutuhkan waktu selama di Window '95. Serius sekali, Word? Berapa banyak sampah baru yang sebenarnya dibutuhkan oleh 80% basis pengguna mereka dalam 18 tahun? Cukup yakin kami sudah melakukan pemeriksaan ejaan pra-jendela, bukan? Tapi kami berbicara memori yang belum tentu cepat jika Anda punya banyak jadi saya ngelantur.
Tapi tentu saja jika Anda bisa menyelesaikan aplikasi dalam 2 minggu dengan biaya mungkin beberapa megabyte tambahan daripada 2 tahun untuk mendapatkan versi kebutuhan-hanya-beberapa-K, ada baiknya mempertimbangkan bagaimana beberapa meg dibandingkan dengan ( Saya kira) 4-12 pertunjukan di mesin pengguna rata-rata sebelum mengejek gagasan menjadi sangat ceroboh.
Tapi apa hubungannya ini dengan Scala di luar pertanyaan pengorbanan? Hanya karena ini adalah pengumpulan sampah, tidak berarti Anda tidak harus selalu berusaha untuk berpikir tentang aliran data dalam hal apa yang ada dalam cakupan dan penutupan dan apakah itu harus dibiarkan duduk atau digunakan sedemikian rupa sehingga akan menjadi dideallocated oleh GC ketika tidak lagi diperlukan. Itu sesuatu yang bahkan kami para pengembang web JavaScript UI harus pikirkan dan mudah-mudahan akan terus berlanjut saat kami menyebar ke domain masalah lain seperti kanker yang paham perf (Anda semua harus terbunuh dengan Flash atau Applet atau sesuatu ketika Anda memiliki kesempatan) bahwa kita.
sumber
Manajemen memori (atau kontrol) sebenarnya adalah alasan utama saya menggunakan C dan C ++.
Memori tidak cepat. Kami masih melihat sejumlah kecil register, seperti cache data 32KB untuk L1 di i7, 256KB untuk L2, dan 2MB untuk L3 / core. Yang mengatakan:
Penggunaan memori pada tingkat umum, mungkin tidak. Saya sedikit tidak praktis dalam hal saya tidak suka ide notepad yang mengambil, katakanlah, 50 megabita DRAM dan ratusan megabita ruang hard disk, meskipun saya punya itu untuk cadangan dan lebih banyak. Saya sudah ada untuk waktu yang lama dan itu terasa aneh dan agak menjengkelkan bagi saya untuk melihat aplikasi sederhana seperti itu mengambil memori yang relatif begitu banyak untuk apa yang harus dilakukan dengan kilobyte. Yang mengatakan, aku mungkin bisa hidup dengan diriku sendiri jika aku menghadapi hal seperti itu jika masih bagus dan responsif.
Alasan mengapa manajemen memori penting bagi saya di bidang saya bukan untuk mengurangi penggunaan memori secara umum. Ratusan megabyte penggunaan memori tidak serta merta memperlambat aplikasi dengan cara apa pun yang tidak sepele jika tidak ada memori yang sering diakses (mis: hanya dengan klik tombol atau bentuk input pengguna lain, yang sangat jarang terjadi kecuali Anda berbicara tentang pemain Starcraft Korea yang mungkin mengklik tombol sejuta kali per detik).
Alasan pentingnya dalam bidang saya adalah untuk mendapatkan memori yang kencang dan berdekatan yang sangat sering diakses (mis: dilingkarkan di setiap frame tunggal) di jalur kritis tersebut. Kami tidak ingin memiliki cache miss setiap kali kami mengakses hanya satu dari sejuta elemen yang perlu semua diakses dalam satu loop setiap frame tunggal. Ketika kita memindahkan memori ke hierarki dari memori lambat ke memori cepat dalam potongan besar, katakanlah garis cache 64 byte, akan sangat membantu jika 64 byte itu semua berisi data yang relevan, jika kita dapat memasukkan beberapa elemen senilai data ke dalam 64 byte itu, dan jika pola akses kami sedemikian rupa sehingga kami menggunakan semuanya sebelum data diusir.
Data yang sering diakses untuk sejuta elemen mungkin hanya menjangkau 20 megabyte meskipun kami memiliki gigabytes. Itu masih membuat dunia perbedaan dalam frame rate pengulangan data yang setiap frame diambil jika memori ketat dan berdekatan untuk meminimalkan kesalahan cache, dan di situlah manajemen memori / kontrol sangat berguna. Contoh visual sederhana pada bola dengan beberapa juta simpul:
Di atas sebenarnya lebih lambat dari versi saya yang bisa berubah karena ini menguji representasi struktur data persisten dari sebuah mesh, tetapi dengan itu selain itu, saya dulu berjuang untuk mencapai frame rate seperti itu bahkan pada setengah data itu (memang perangkat kerasnya telah menjadi lebih cepat sejak perjuangan saya. ) karena saya tidak memahami meminimalkan kesalahan cache dan penggunaan memori untuk data mesh. Jerat adalah beberapa struktur data tersulit yang pernah saya tangani dalam hal ini karena mereka menyimpan begitu banyak data yang saling bergantung yang harus tetap sinkron seperti poligon, tepi, simpul, sebanyak peta tekstur yang ingin dilampirkan pengguna, bobot tulang, peta warna, set pilihan, target morph, bobot tepi, bahan poligon, dll. dll. dll.
Saya telah merancang dan mengimplementasikan sejumlah sistem mesh dalam beberapa dekade terakhir dan kecepatannya seringkali sangat proporsional dengan penggunaan memori mereka. Meskipun saya bekerja dengan memori yang jauh lebih banyak daripada ketika saya mulai, sistem mesh baru saya lebih dari 10 kali lebih cepat dari desain pertama saya (hampir 20 tahun yang lalu) dan sebagian besar karena mereka menggunakan sekitar 1/10 dari Ingatan. Versi terbaru bahkan menggunakan kompresi yang diindeks untuk menjejalkan data sebanyak mungkin, dan meskipun memproses overhead dekompresi, kompresi sebenarnya meningkatkan kinerja karena, sekali lagi, kami memiliki memori cepat yang sangat sedikit berharga. Sekarang saya dapat memuat jutaan poligon mesh dengan koordinat tekstur, edge edge, penugasan material, dll. Bersama dengan indeks spasial untuknya dalam sekitar 30 megabyte.
Inilah prototipe yang bisa berubah-ubah dengan lebih dari 8 juta segi empat dan skema pembagian banyak pada i3 dengan GF 8400 (ini dari beberapa tahun yang lalu). Ini lebih cepat daripada versi saya yang tidak dapat diubah tetapi tidak digunakan dalam produksi karena saya telah menemukan versi yang tidak dapat diubah ini jauh lebih mudah untuk dipertahankan dan performa yang dicapai tidak terlalu buruk. Perhatikan bahwa bingkai gambar tidak menunjukkan segi, tetapi tambalan (kabel sebenarnya kurva, jika tidak seluruh jala akan menjadi hitam pekat), meskipun semua titik dalam segi diubah oleh sikat.
Jadi, saya hanya ingin menunjukkan beberapa hal di atas untuk menunjukkan beberapa contoh nyata dan area di mana manajemen memori sangat membantu dan juga mudah-mudahan orang tidak berpikir saya hanya berbicara tentang pantat saya. Saya cenderung sedikit kesal ketika orang mengatakan memori sangat banyak dan murah, karena itu berbicara tentang memori lambat seperti DRAM dan hard drive. Ini masih sangat kecil dan sangat berharga ketika kita berbicara tentang memori cepat, dan kinerja untuk jalur yang benar-benar kritis (yaitu, kasus umum, bukan untuk semuanya) berkaitan dengan bermain dengan sejumlah kecil memori cepat dan menggunakannya sebaik mungkin. .
Untuk hal semacam ini, sangat membantu untuk bekerja dengan bahasa yang memungkinkan Anda mendesain objek tingkat tinggi seperti C ++, misalnya, sementara masih dapat menyimpan objek-objek ini dalam satu atau lebih array yang berdekatan dengan jaminan bahwa memori dari semua objek tersebut akan direpresentasikan secara berdekatan dan tanpa overhead memori yang tidak diperlukan per objek (mis: tidak semua objek memerlukan refleksi atau pengiriman virtual). Ketika Anda benar-benar pindah ke area yang sangat kritis terhadap kinerja, itu sebenarnya menjadi dorongan produktivitas untuk memiliki kontrol memori seperti itu, katakanlah, mengutak-atik kumpulan objek dan menggunakan tipe data primitif untuk menghindari overhead objek, biaya GC, dan untuk menjaga agar memori sering diakses bersama berdekatan.
Jadi manajemen / kontrol memori (atau ketiadaannya) sebenarnya adalah alasan utama dalam kasus saya untuk memilih bahasa apa yang paling produktif memungkinkan saya untuk mengatasi masalah. Saya benar-benar menulis bagian kode saya yang tidak kritis terhadap kinerja, dan untuk itu saya cenderung menggunakan Lua yang cukup mudah untuk di-embed dari C.
sumber