Mungkinkah lebih efisien untuk sistem secara umum untuk menghilangkan Stacks dan hanya menggunakan Heap untuk manajemen memori?

14

Sepertinya saya bahwa segala sesuatu yang dapat dilakukan dengan tumpukan dapat dilakukan dengan tumpukan, tetapi tidak semua yang bisa dilakukan dengan tumpukan dapat dilakukan dengan tumpukan. Apakah itu benar? Maka demi kesederhanaan, dan bahkan jika kita kehilangan sedikit kinerja dengan beban kerja tertentu, tidak bisakah lebih baik hanya dengan satu standar (yaitu, tumpukan)?

Pikirkan pertukaran antara modularitas dan kinerja. Saya tahu itu bukan cara terbaik untuk menggambarkan skenario ini, tetapi secara umum tampaknya kesederhanaan pemahaman dan desain bisa menjadi pilihan yang lebih baik bahkan jika ada potensi kinerja yang lebih baik.

Dark Templar
sumber
1
Di C dan C ++, Anda perlu secara eksplisit mengalokasikan memori yang dialokasikan pada heap. Itu tidak sederhana.
user16764
Saya menggunakan implementasi C # yang profiling mengungkapkan bahwa objek tumpukan dialokasikan di daerah seperti tumpukan dengan pengumpulan sampah yang mengerikan. Solusi saya? Pindahkan segala sesuatu yang mungkin (misalnya variabel loop, variabel sementara, dll.) Ke memori tumpukan persisten. Membuat program memakan 10x RAM dan berjalan dengan kecepatan 10x.
imallett
@IanMallett: Saya tidak mengerti penjelasan Anda tentang masalah dan solusinya. Apakah Anda memiliki tautan dengan informasi lebih lanjut di suatu tempat? Biasanya saya menemukan alokasi berbasis tumpukan lebih cepat.
Frank Hileman
@ Frankhileman masalah mendasarnya adalah ini: implementasi C # yang saya gunakan memiliki kecepatan pengumpulan sampah yang sangat buruk. "Solusi" adalah membuat semua variabel bertahan sehingga pada saat runtime tidak ada operasi memori yang terjadi. Saya menulis sebuah opini beberapa waktu lalu tentang pengembangan C # / XNA secara umum yang juga membahas beberapa konteks.
imallett
@IanMallett: terima kasih. Sebagai mantan pengembang C / C ++ yang sebagian besar menggunakan C # hari ini, pengalaman saya sangat berbeda. Saya menemukan bahwa perpustakaan adalah masalah terbesar. Kedengarannya seperti platform XBox360 setengah matang untuk pengembang .net. Biasanya ketika saya memiliki masalah GC, saya beralih ke pooling. Itu membantu.
Frank Hileman

Jawaban:

30

Tumpukan buruk pada alokasi memori cepat dan deallokasi. Jika Anda ingin mengambil banyak memori dalam durasi terbatas, tumpukan bukanlah pilihan terbaik Anda. Tumpukan, dengan algoritma alokasi / dealokasi super-sederhana, secara alami unggul dalam hal ini (bahkan lebih lagi jika itu dimasukkan ke dalam perangkat keras), itulah sebabnya orang menggunakannya untuk hal-hal seperti meneruskan argumen ke fungsi dan menyimpan variabel lokal - yang paling Kelemahan penting adalah bahwa ia memiliki ruang terbatas, dan dengan demikian menyimpan benda-benda besar di dalamnya, atau mencoba menggunakannya untuk benda berumur panjang, keduanya adalah ide yang buruk.

Menyingkirkan tumpukan sepenuhnya demi menyederhanakan bahasa pemrograman adalah cara IMO yang salah - pendekatan yang lebih baik adalah mengabstraksi perbedaan, biarkan kompiler mencari tahu jenis penyimpanan yang akan digunakan, sementara programmer mengumpulkan lebih tinggi- konstruksi tingkat yang lebih dekat dengan cara manusia berpikir - dan pada kenyataannya, bahasa tingkat tinggi seperti C #, Java, Python dll melakukan hal ini. Mereka menawarkan sintaks yang hampir identik untuk objek yang dialokasikan heap dan primitif yang dialokasikan stack ('tipe referensi' vs 'tipe nilai' dalam .NET lingo), baik sepenuhnya transparan, atau dengan beberapa perbedaan fungsional yang harus Anda pahami untuk menggunakan bahasa dengan benar (tetapi Anda tidak benar-benar harus tahu bagaimana tumpukan dan tumpukan bekerja secara internal).

tammmer
sumber
2
WOW INI BAIK :) Benar-benar ringkas dan informatif untuk pemula!
Dark Templar
1
Pada banyak CPU tumpukan ditangani dalam perangkat keras, yang merupakan masalah di luar bahasa tetapi memainkan peran besar pada saat dijalankan.
Patrick Hughes
@ Patrick Hughes: Ya, tetapi Heap juga terletak di perangkat keras, bukan?
Dark Templar
@Dark Apa yang Patrick mungkin ingin katakan adalah bahwa arsitektur seperti x86 memiliki register khusus untuk mengelola stack dan instruksi khusus untuk meletakkan atau menghapus sesuatu pada / dari stack. Itu membuatnya cukup cepat.
FUZxxl
3
@Donal Fellows: Semua benar. Tetapi intinya adalah bahwa tumpukan dan tumpukan keduanya memiliki poin kuat dan lemah mereka, dan menggunakannya sesuai akan menghasilkan kode yang paling efisien.
Pelaku
8

Sederhananya, tumpukan bukanlah sedikit kinerja. Ini ratusan atau ribuan kali lebih cepat dari heap. Selain itu, sebagian besar mesin modern memiliki dukungan perangkat keras untuk stack (seperti x86) dan fungsionalitas perangkat keras untuk misalnya tumpukan panggilan tidak dapat dihapus.

DeadMG
sumber
Apa maksud Anda ketika Anda mengatakan bahwa mesin modern memiliki dukungan perangkat keras untuk stack? Tumpukan itu sendiri sudah DI perangkat keras, bukan?
Dark Templar
1
x86 memiliki register dan instruksi khusus untuk menangani stack. x86 tidak memiliki dukungan untuk tumpukan - hal-hal seperti itu dibuat oleh OS.
Pubby
8

Tidak

Area tumpukan dalam C ++ sangat cepat dibandingkan. Saya berani tidak pengembang C ++ berpengalaman akan terbuka untuk menonaktifkan fungsi itu.

Dengan C ++, Anda memiliki pilihan dan Anda memiliki kendali. Para desainer tidak terlalu tertarik untuk memperkenalkan fitur-fitur yang menambah waktu atau ruang eksekusi yang signifikan.

Melatih pilihan itu

Jika Anda ingin membangun perpustakaan atau program yang mengharuskan setiap objek dialokasikan secara dinamis, Anda bisa melakukannya dengan C ++. Ini akan berjalan relatif lambat, tetapi Anda kemudian dapat memiliki 'modularitas' itu. Bagi kita semua, modularitas selalu opsional, memperkenalkannya sesuai kebutuhan karena keduanya diperlukan untuk implementasi yang baik / cepat.

Alternatif

Ada bahasa lain yang mengharuskan penyimpanan untuk setiap objek dibuat di heap; itu sangat lambat, sehingga kompromi desain (program dunia nyata) dengan cara yang lebih buruk daripada harus belajar keduanya (IMO).

Keduanya penting, dan C ++ memberi Anda kekuatan menggunakan keduanya secara efektif untuk setiap skenario yang diberikan. Karena itu, bahasa C ++ mungkin tidak ideal untuk desain Anda, jika faktor-faktor ini dalam OP Anda penting bagi Anda (misalnya, baca tentang bahasa tingkat yang lebih tinggi).

justin
sumber
Tumpukan sebenarnya kecepatan yang sama dengan tumpukan, tetapi tidak memiliki dukungan perangkat keras khusus untuk alokasi. Di sisi lain, ada banyak cara untuk mempercepat tumpukan (tergantung pada sejumlah kondisi yang menjadikannya teknik khusus pakar).
Donal Fellows
@DonalFellows: Dukungan perangkat keras untuk tumpukan tidak relevan. Yang penting adalah mengetahui bahwa setiap kali sesuatu dirilis seseorang dapat melepaskan apa pun yang dialokasikan setelahnya. Beberapa bahasa pemrograman tidak memiliki tumpukan yang secara mandiri dapat membebaskan objek, tetapi sebaliknya hanya memiliki metode "semuanya bebas dialokasikan setelah".
supercat
6

Maka demi kesederhanaan, dan bahkan jika kita kehilangan sedikit kinerja dengan beban kerja tertentu, tidak bisakah lebih baik hanya dengan satu standar (yaitu, tumpukan)?

Sebenarnya hit kinerja kemungkinan besar!

Seperti yang orang lain tunjukkan tumpukan adalah struktur yang sangat efisien untuk mengelola data yang mematuhi aturan LIFO (terakhir masuk pertama keluar). Alokasi / pembebasan memori pada stack biasanya hanya perubahan pada register pada CPU. Mengubah register hampir selalu merupakan salah satu operasi tercepat yang dapat dilakukan prosesor.

Tumpukan biasanya struktur data yang cukup kompleks dan mengalokasikan / membebaskan memori akan membutuhkan banyak instruksi untuk melakukan semua pembukuan terkait. Lebih buruk lagi, dalam implementasi umum, setiap panggilan untuk bekerja dengan heap memiliki potensi untuk menghasilkan panggilan ke sistem operasi. Panggilan sistem operasi sangat memakan waktu! Program biasanya harus beralih dari mode pengguna ke mode kernel, dan setiap kali ini terjadi, sistem operasi dapat memutuskan bahwa program lain memiliki kebutuhan yang lebih mendesak, dan bahwa program Anda harus menunggu.

Charles E. Grant
sumber
5

Simula menggunakan heap untuk semuanya. Menempatkan segala sesuatu di tumpukan selalu menginduksi satu tingkat lebih banyak tipuan untuk variabel lokal, dan itu memberi tekanan tambahan pada Pengumpul Sampah (Anda harus memperhitungkan bahwa Pengumpul Sampah benar-benar tersedot saat itu). Itulah sebagian alasan mengapa Bjarne menemukan C ++.

fredoverflow
sumber
Jadi pada dasarnya C ++ hanya menggunakan heap juga?
Dark Templar
2
@Dark: Apa? Tidak. Kurangnya tumpukan di Simula adalah inspirasi untuk melakukannya dengan lebih baik.
fredoverflow
Ah, aku mengerti maksudmu sekarang! Terima kasih +1 :)
Dark Templar
3

Tumpukan sangat efisien untuk data LIFO, seperti meta-data yang terkait dengan panggilan fungsi, misalnya. Tumpukan juga memanfaatkan fitur desain yang melekat pada CPU. Karena kinerja pada level ini sangat mendasar bagi hampir semua hal lain dalam suatu proses, mengambil hit "kecil" pada level itu akan menyebar sangat luas. Selain itu, tumpukan memori dapat dipindahkan oleh OS, yang akan mematikan tumpukan. Sementara stack dapat diimplementasikan dalam heap, itu membutuhkan overhead yang akan memengaruhi setiap bagian dari proses pada level paling granular.

Kylben
sumber
2

"efisien" dalam hal Anda menulis kode mungkin, tetapi tentu saja tidak dalam hal efisiensi perangkat lunak Anda. Alokasi tumpukan pada dasarnya gratis (hanya dibutuhkan beberapa instruksi mesin untuk memindahkan stack pointer dan menyimpan ruang pada stack untuk variabel lokal).

Karena alokasi tumpukan hampir tidak memerlukan waktu, alokasi bahkan pada tumpukan yang sangat efisien akan 100k (jika tidak 1M +) kali lebih lambat.

Sekarang bayangkan berapa banyak variabel lokal dan struktur data lain yang digunakan aplikasi tipikal. Setiap "i" kecil yang Anda gunakan sebagai penghitung putaran dialokasikan sejuta kali lebih lambat.

Tentu jika perangkat kerasnya cukup cepat, Anda bisa menulis aplikasi yang hanya menggunakan heap. Tetapi sekarang gambarkan aplikasi seperti apa yang bisa Anda tulis jika Anda memanfaatkan heap dan menggunakan perangkat keras yang sama.

DXM
sumber
Ketika Anda mengatakan "bayangkan berapa banyak variabel lokal dan struktur data lain yang digunakan aplikasi tipikal" apa struktur data lain yang Anda maksud?
Dark Templar
1
Apakah nilai "100k" dan "1M +" entah bagaimana ilmiah? Atau itu hanya cara untuk mengatakan "banyak"?
Bruno Reis
@ Bruno - IMHO angka 100K dan 1M yang saya gunakan sebenarnya adalah perkiraan konservatif untuk membuktikan suatu hal. Jika Anda terbiasa dengan VS dan C ++, tulislah sebuah program yang mengalokasikan 100 byte pada stack dan tulislah yang mengalokasikan 100 byte pada heap. Kemudian beralih ke tampilan pembongkaran dan cukup hitung jumlah instruksi perakitan yang dibutuhkan setiap alokasi. Heap operations biasanya adalah beberapa pemanggilan fungsi ke windows DLL, ada bucket dan daftar tertaut dan kemudian ada penggabungan dan algoritma lainnya. Dengan stack, dapat direduksi menjadi satu instruksi perakitan "tambah esp, 100" ...
DXM
2
"100k (jika tidak 1M +) kali lebih lambat"? Itu agak berlebihan. Biarlah dua perintah besarnya lebih lambat, mungkin tiga, tapi hanya itu. Setidaknya, Linux saya dapat melakukan alokasi tumpukan 100 juta (+ beberapa instruksi sekitarnya) dalam waktu kurang dari 6 detik pada inti i5, yang tidak bisa lebih dari beberapa ratus instruksi per alokasi - sebenarnya, itu hampir pasti kurang. Jika enam kali lipat lebih lambat dari stack ada sesuatu yang salah dengan implementasi heap OS. Tentu ada banyak yang salah dengan Windows, tapi itu ...
leftaroundabout
1
moderator mungkin akan mematikan seluruh utas komentar ini. Jadi, inilah kesepakatannya, saya mengakui bahwa angka aktual ditarik dari ...., tapi mari kita setujui faktornya benar-benar besar dan tidak membuat lebih banyak komentar :)
DXM
2

Anda mungkin tertarik pada "Pengumpulan Sampah Cepat, tetapi Tumpukan Lebih Cepat".

http://dspace.mit.edu/bitstream/handle/1721.1/6622/AIM-1462.ps.Z

Jika saya membacanya dengan benar, orang-orang ini memodifikasi kompiler C untuk mengalokasikan "stack frames" di heap, dan kemudian menggunakan pengumpulan sampah untuk tidak mengalokasikan frame alih-alih membuka stack.

"Stack frames" yang dialokasikan tumpukan mengungguli "stack frames" yang dialokasikan dengan tegas.

Bruce Ediger
sumber
1

Bagaimana tumpukan panggilan akan berfungsi pada tumpukan? Pada dasarnya, Anda harus mengalokasikan tumpukan pada tumpukan di setiap program, jadi mengapa tidak ada perangkat keras OS + yang melakukannya untuk Anda?

Jika Anda ingin segala sesuatunya menjadi sangat sederhana dan efisien, cukup berikan memori kepada pengguna dan biarkan mereka menanganinya. Tentu saja, tidak ada yang ingin menerapkan semuanya sendiri dan itulah sebabnya kami memiliki setumpuk dan tumpukan.

Pubby
sumber
Sebenarnya "tumpukan panggilan" bukanlah fitur yang diperlukan dari lingkungan runtime bahasa pemrograman. mis. Implementasi langsung dari bahasa fungsional yang malas dievaluasi dengan pengurangan grafik (yang telah saya kodekan) tidak memiliki tumpukan panggilan. Tetapi tumpukan panggilan adalah teknik yang sangat berguna dan banyak digunakan, terutama karena prosesor modern menganggap Anda menggunakannya dan dioptimalkan untuk penggunaannya.
Ben
@ Ben - walaupun benar (dan merupakan hal yang baik) untuk mengabstraksikan hal-hal seperti alokasi memori dari suatu bahasa, ini tidak mengubah arsitektur komputer yang sekarang berlaku. Karenanya, kode pengurangan grafik Anda masih akan menggunakan tumpukan saat dijalankan - suka atau tidak.
Ingo
@Ingo Tidak benar-benar berarti. Tentu, OS akan menginisialisasi bagian memori yang secara tradisional disebut "tumpukan", dan akan ada register yang menunjuk ke sana. Tetapi fungsi-fungsi dalam bahasa sumber tidak berakhir sebagai bingkai tumpukan dalam urutan panggilan. Eksekusi fungsi sepenuhnya diwakili oleh manipulasi struktur data di heap. Bahkan tanpa menggunakan optimasi panggilan terakhir tidak mungkin untuk "meluap tumpukan". Itulah yang saya maksud ketika saya mengatakan tidak ada yang mendasar tentang "tumpukan panggilan".
Ben
Saya tidak berbicara tentang fungsi-fungsi bahasa sumber tetapi fungsi-fungsi dalam juru bahasa (atau apa pun) yang benar-benar melakukan pengurangan grafik. Mereka akan membutuhkan tumpukan. Ini terbukti, karena perangkat keras kontemporer tidak melakukan pengurangan grafik. Oleh karena itu, algoritma pengurangan grafik Anda pada akhirnya dipetakan ke mesin ode, dan saya yakin ada panggilan subrutin di antara mereka. QED.
Ingo
1

Diperlukan tumpukan dan tumpukan. Mereka digunakan dalam situasi yang berbeda, misalnya:

  1. Alokasi heap memiliki batasan bahwa sizeof (a [0]) == sizeof (a [1])
  2. Alokasi tumpukan memiliki batasan bahwa sizeof (a) adalah konstanta waktu kompilasi
  3. Alokasi heap dapat melakukan loop, grafik dll struktur data yang kompleks
  4. Alokasi tumpukan dapat melakukan pohon berukuran waktu kompilasi
  5. Heap membutuhkan pelacakan kepemilikan
  6. Alokasi tumpukan dan deallokasi otomatis
  7. Memori tumpukan dapat dengan mudah diteruskan dari satu lingkup ke lingkup lain melalui pointer
  8. Stack memory bersifat lokal untuk setiap fungsi dan objek harus dipindahkan ke ruang lingkup atas untuk memperpanjang masa pakainya (atau disimpan di dalam objek, bukan di dalam fungsi anggota)
  9. Heap buruk untuk kinerja
  10. Stack cukup cepat
  11. Objek tumpukan dikembalikan dari fungsi melalui pointer yang mengambil kepemilikan. Atau shared_ptrs.
  12. Objek tumpukan dikembalikan dari fungsi melalui referensi yang tidak memiliki kepemilikan.
  13. Heap mengharuskan mencocokkan setiap yang baru dengan jenis hapus atau hapus yang benar []
  14. Stack objek menggunakan RAII dan daftar inisialisasi konstruktor
  15. Objek tumpukan dapat diinisialisasi setiap titik di dalam suatu fungsi, dan tidak dapat menggunakan parameter konstruktor
  16. Objek tumpukan menggunakan parameter konstruktor untuk inisialisasi
  17. Heap menggunakan array dan ukuran array dapat berubah saat runtime
  18. Stack adalah untuk objek tunggal, dan ukuran ditetapkan pada waktu kompilasi

Pada dasarnya mekanisme tidak dapat dibandingkan sama sekali karena begitu banyak detail yang berbeda. Satu-satunya hal yang sama dengan mereka adalah bahwa mereka berdua menangani memori entah bagaimana.

tp1
sumber
1

Komputer modern memiliki beberapa lapisan memori cache selain sistem memori utama yang besar dan lambat. Satu dapat membuat lusinan akses ke memori cache tercepat dalam waktu yang diperlukan untuk membaca atau menulis satu byte dari sistem memori utama. Dengan demikian, mengakses satu lokasi seribu kali jauh lebih cepat daripada mengakses 1.000 (atau bahkan 100) lokasi independen masing-masing satu kali. Karena sebagian besar aplikasi berulang kali mengalokasikan dan membatalkan alokasi sejumlah kecil memori di dekat bagian atas tumpukan, lokasi di bagian atas tumpukan digunakan dan digunakan kembali dalam jumlah yang sangat besar, sehingga sebagian besar (99% + dalam aplikasi khas) akses stack dapat ditangani menggunakan memori cache.

Sebaliknya, jika suatu aplikasi berulang kali membuat dan meninggalkan objek tumpukan untuk menyimpan informasi kelanjutan, setiap versi dari setiap objek tumpukan yang pernah dibuat harus dituliskan ke memori utama. Bahkan jika sebagian besar objek tersebut akan sama sekali tidak berguna pada saat CPU ingin mendaur ulang halaman cache yang mereka mulai, CPU tidak akan memiliki cara untuk mengetahui hal itu. Akibatnya, CPU harus membuang banyak waktu melakukan memori lambat menulis informasi yang tidak berguna. Bukan resep untuk kecepatan.

Hal lain yang perlu dipertimbangkan adalah bahwa dalam banyak kasus, berguna untuk mengetahui bahwa referensi objek yang diteruskan ke rutin tidak akan digunakan setelah rutin keluar. Jika parameter dan variabel lokal dilewatkan melalui tumpukan, dan jika inspeksi kode rutin mengungkapkan bahwa itu tidak bertahan salinan referensi yang lewat, maka kode yang memanggil rutin dapat memastikan bahwa jika tidak ada referensi luar ke objek ada sebelum panggilan, tidak ada yang akan ada sesudahnya. Sebaliknya, jika parameter dilewatkan melalui objek tumpukan, konsep seperti "setelah pengembalian rutin" menjadi agak lebih samar, karena jika kode menyimpan salinan kelanjutan, akan mungkin bagi rutin untuk "kembali" lebih dari sekali mengikuti panggilan tunggal.

supercat
sumber