Mengapa ukuran tumpukan di C # tepat 1 MB?

102

PC saat ini memiliki RAM fisik dalam jumlah besar tetapi tetap saja, ukuran tumpukan C # hanya 1 MB untuk proses 32-bit dan 4 MB untuk proses 64-bit ( Kapasitas tumpukan dalam C # ).

Mengapa ukuran tumpukan di CLR masih sangat terbatas?

Dan mengapa tepatnya 1 MB (4 MB) (dan bukan 2 MB atau 512 KB)? Mengapa diputuskan untuk menggunakan jumlah ini?

Saya tertarik dengan pertimbangan dan alasan di balik keputusan itu .

Nikolay Kostov
sumber
6
Ukuran tumpukan default untuk proses 64bit adalah 4MB, itu adalah 1MB untuk proses 32bit. Anda dapat mengubah ukuran tumpukan utas utama dengan mengubah nilai di header PE-nya. Anda juga dapat menentukan ukuran tumpukan dengan menggunakan kelebihan Threadkonstruktor yang tepat. TAPI, ini menimbulkan pertanyaan, mengapa Anda membutuhkan tumpukan yang lebih besar?
Yuval Itzchakov
2
Terima kasih, sudah diedit. :) Pertanyaannya bukan tentang bagaimana menggunakan ukuran tumpukan yang lebih besar, tetapi mengapa ukuran tumpukan diputuskan menjadi 1 MB (4 MB) .
Nikolay Kostov
8
Karena setiap utas akan mendapatkan ukuran tumpukan ini secara default, dan sebagian besar utas tidak membutuhkan sebanyak itu. Saya baru saja mem-boot PC saya dan sistem saat ini menjalankan 1200 utas. Sekarang lakukan matematika;)
Lucas Trzesniewski
2
@LucasTrzesniewski Tidak hanya itu, harus menular dalam ingatan . Perhatikan semakin besar ukuran tumpukan, semakin sedikit utas yang dapat dibuat oleh proses Anda di ruang alamat virtualnya.
Yuval Itzchakov
Tidak yakin tentang "persis" 1 MB: di Windows 8.1 saya, aplikasi konsol .NET Core 3.1 memiliki 1572864ukuran tumpukan default byte (Diperoleh menggunakan GetCurrentThreadStackLimits Win32 API). Saya bisa stackallockira - kira 1500000byte tanpa StackOverflowException.
George Chakhidze

Jawaban:

210

masukkan deskripsi gambar di sini

Anda sedang melihat pria yang membuat pilihan itu. David Cutler dan timnya memilih satu megabyte sebagai ukuran tumpukan default. Tidak ada hubungannya dengan .NET atau C #, ini dipaku ketika mereka membuat Windows NT. Satu megabyte adalah apa yang dipilihnya ketika header EXE dari sebuah program atau panggilan winapi CreateThread () tidak menentukan ukuran tumpukan secara eksplisit. Yang merupakan cara normal, hampir semua programmer membiarkan OS untuk memilih ukurannya.

Pilihan itu mungkin mendahului desain Windows NT, sejarahnya terlalu kabur tentang ini. Alangkah baiknya jika Cutler mau menulis buku tentang itu, tapi dia tidak pernah menjadi penulis. Dia sangat berpengaruh pada cara kerja komputer. Desain OS pertamanya adalah RSX-11M, sistem operasi 16-bit untuk komputer DEC (Digital Equipment Corporation). Ini sangat mempengaruhi CP / M Gary Kildall, OS pertama yang layak untuk mikroprosesor 8-bit. Yang sangat mempengaruhi MS-DOS.

Desain berikutnya adalah VMS, sistem operasi untuk prosesor 32-bit dengan dukungan memori virtual. Sangat sukses. Yang berikutnya dibatalkan oleh DEC sekitar saat perusahaan mulai bubar, tidak mampu bersaing dengan perangkat keras PC murah. Isyarat Microsoft, mereka memberinya tawaran yang tidak bisa dia tolak. Banyak rekan kerjanya juga bergabung. Mereka bekerja pada VMS v2, lebih dikenal sebagai Windows NT. DEC menjadi kesal karenanya, uang berpindah tangan untuk menyelesaikannya. Apakah VMS sudah memilih satu megabyte adalah sesuatu yang saya tidak tahu, saya hanya cukup mengenal RSX-11. Itu tidak mungkin.

Sejarah yang cukup. Satu megabyte itu banyak , utas asli jarang menghabiskan lebih dari beberapa kilobyte. Jadi satu megabyte sebenarnya agak boros. Bagaimanapun juga, jenis pemborosan yang dapat Anda lakukan pada sistem operasi memori virtual dengan halaman permintaan, megabyte itu hanyalah memori virtual . Hanya angka untuk prosesor, masing-masing satu untuk setiap 4096 byte. Anda tidak pernah benar-benar menggunakan memori fisik, RAM di mesin, sampai Anda benar-benar mengatasinya.

Ini lebih berlebihan dalam program .NET karena ukuran satu megabyte awalnya dipilih untuk mengakomodasi program asli. Yang cenderung membuat bingkai tumpukan besar, menyimpan string dan buffer (array) di tumpukan juga. Terkenal sebagai vektor serangan malware, buffer overflow dapat memanipulasi program dengan data. Bukan cara kerja program .NET, string dan array dialokasikan pada heap GC dan pengindeksan diperiksa. Satu-satunya cara untuk mengalokasikan ruang pada stack dengan C # adalah dengan kata kunci stackalloc yang tidak aman .

Satu-satunya penggunaan tumpukan yang tidak sepele di .NET adalah dengan jitter. Ini menggunakan tumpukan utas Anda untuk mengkompilasi MSIL ke kode mesin tepat waktu. Saya belum pernah melihat atau memeriksa berapa banyak ruang yang dibutuhkannya, ini lebih tergantung pada sifat kode dan apakah pengoptimal diaktifkan atau tidak, tetapi beberapa puluh kilobyte adalah tebakan kasar. Begitulah cara situs web ini mendapatkan namanya, tumpukan overflow dalam program .NET cukup fatal. Tidak ada cukup ruang tersisa (kurang dari 3 kilobyte) untuk tetap andal JIT kode apa pun yang mencoba menangkap pengecualian. Kaboom to desktop adalah satu-satunya pilihan.

Last but not least, program .NET melakukan sesuatu yang sangat tidak produktif dengan stack. CLR akan melakukan tumpukan utas. Itu adalah kata yang mahal yang artinya tidak hanya menyimpan ukuran tumpukan, tetapi juga memastikan bahwa ruang dicadangkan dalam file halaman sistem operasi sehingga tumpukan selalu dapat ditukar saat diperlukan. Gagal mengkomit adalah kesalahan fatal dan menghentikan program tanpa syarat. Itu hanya terjadi pada mesin dengan RAM sangat sedikit yang menjalankan terlalu banyak proses, mesin seperti itu akan berubah menjadi molase sebelum program mulai mati. Masalah yang mungkin terjadi 15+ tahun yang lalu, bukan hari ini. Pemrogram yang menyetel program mereka untuk bertindak seperti mobil balap F1 menggunakan <disableCommitThreadStack>elemen dalam file .config mereka.

Fwiw, Cutler tidak berhenti mendesain sistem operasi. Foto itu dibuat saat dia mengerjakan Azure.


Pembaruan, saya perhatikan bahwa .NET tidak lagi melakukan tumpukan. Tidak yakin kapan atau mengapa ini terjadi, sudah terlalu lama sejak saya memeriksanya. Saya menduga perubahan desain ini terjadi di sekitar .NET 4.5. Perubahan yang cukup masuk akal.

Hans Passant
sumber
3
wrt ke komentar Anda The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword.- Apakah variabel lokal misalnya yang intdideklarasikan di dalam metode tidak disimpan di stack? Saya pikir mereka.
RBT
2
Baik. Sekarang saya mengerti bahwa stack-frame bukan satu-satunya pilihan penyimpanan untuk variabel lokal suatu fungsi. Ini dapat disimpan pada bingkai tumpukan seperti yang disarankan di salah satu poin peluru Anda. Hans yang sangat mencerahkan. Saya tidak dapat mengucapkan cukup banyak terima kasih karena telah menulis posting yang berwawasan seperti itu. Jujur stack adalah abstraksi besar untuk pemrograman secara umum hanya untuk menghindari kerumitan yang tidak perlu.
RBT
Deskripsi yang sangat detail @Hans. Saya hanya ingin tahu berapa nilai minimum yang mungkin untuk maxStackSizesebuah utas? Saya tidak dapat menemukannya di [MSDN] ( msdn.microsoft.com/en-us/library/5cykbwz4(v=vs.110).aspx ). Berdasarkan komentar Anda, tampaknya penggunaan tumpukan sangat minimum dan saya dapat menggunakan nilai terkecil untuk mengakomodasi utas semaksimal mungkin. Terima kasih.
MKR
1
@KFL: Anda dapat menjawab pertanyaan Anda dengan mudah dengan mencobanya!
Eric Lippert
1
Jika perilaku defaultnya adalah tidak lagi mengkomit tumpukan, maka file penurunan harga ini perlu diedit github.com/dotnet/docs/blob/master/docs/framework/…
John Stewien
5

Ukuran tumpukan yang dicadangkan default ditentukan oleh penaut dan dapat diganti oleh pengembang melalui mengubah nilai PE pada waktu penautan atau untuk utas individu dengan menentukan dwStackSizeparameter untuk CreateThreadfungsi WinAPI.

Jika Anda membuat utas dengan ukuran tumpukan awal lebih besar dari atau sama dengan ukuran tumpukan default, maka utas tersebut akan dibulatkan ke kelipatan terdekat yaitu 1 MB.

Mengapa nilainya sama dengan 1 MB untuk proses 32-bit dan 4 MB untuk 64-bit? Saya pikir Anda harus bertanya kepada pengembang, yang merancang Windows, atau menunggu sampai seseorang menjawab pertanyaan Anda.

Mungkin Mark Russinovich tahu itu dan Anda bisa menghubunginya . Mungkin Anda dapat menemukan informasi ini di buku-buku Windows Internals-nya sebelum edisi keenam yang menjelaskan lebih sedikit info tentang tumpukan daripada artikelnya . Atau mungkin Raymond Chen tahu alasannya karena dia menulis hal-hal menarik tentang internal Windows dan sejarahnya. Dia dapat menjawab pertanyaan Anda juga, tetapi Anda harus memposting saran ke Kotak Saran .

Tetapi kali ini saya akan mencoba menjelaskan beberapa kemungkinan alasan mengapa Microsoft memilih nilai-nilai ini menggunakan blog MSDN, Mark's dan Raymond.

Defaultnya memiliki nilai ini mungkin karena pada masa awal PC lambat dan mengalokasikan memori pada stack jauh lebih cepat daripada mengalokasikan memori di heap. Dan karena alokasi tumpukan jauh lebih murah, mereka digunakan, tetapi itu membutuhkan ukuran tumpukan yang lebih besar.

Jadi nilainya adalah ukuran tumpukan cadangan optimal untuk sebagian besar aplikasi. Ini optimal karena memungkinkan untuk membuat banyak panggilan bersarang dan mengalokasikan memori pada stack untuk meneruskan struktur ke fungsi panggilan. Pada saat yang sama memungkinkan untuk membuat banyak utas.

Saat ini nilai-nilai ini banyak digunakan untuk kompatibilitas mundur, karena struktur yang diteruskan sebagai parameter ke fungsi WinAPI masih dialokasikan di stack. Tetapi jika Anda tidak menggunakan alokasi tumpukan maka penggunaan tumpukan utas akan jauh lebih kecil dari default 1 MB dan itu sia-sia seperti yang disebutkan Hans Passant. Dan untuk mencegah hal ini, OS hanya melakukan halaman pertama dari tumpukan (4 KB), jika yang lain tidak ditentukan di header PE aplikasi. Halaman lain dialokasikan sesuai permintaan.

Beberapa aplikasi menimpa ruang alamat yang dicadangkan dan awalnya berkomitmen untuk mengoptimalkan penggunaan memori. Sebagai contoh, ukuran tumpukan maksimum utas proses asli IIS adalah 256 KB ( KB932909 ). Dan penurunan nilai default ini direkomendasikan oleh Microsoft:

Yang terbaik adalah memilih sekecil mungkin ukuran tumpukan dan mengikat tumpukan yang diperlukan agar utas atau fiber berjalan dengan andal. Setiap halaman yang dicadangkan untuk tumpukan tidak dapat digunakan untuk tujuan lain.

Sumber:

  1. Ukuran Thread Stack (Microsoft Docs)
  2. Mendorong Batas Windows: Proses dan Untaian (Mark Russinovich)
  3. Secara default, ukuran tumpukan maksimum utas yang dibuat dalam proses IIS asli adalah 256 KB (KB932909)
Yoh Deadfall
sumber
Jika saya ingin ukuran tumpukan yang lebih besar, saya dapat mengaturnya ( atalasoft.com/cs/blogs/rickm/archive/2008/04/22/… ). Saya ingin mengetahui pertimbangan dan alasan di balik keputusan itu.
Nikolay Kostov
2
Baik. Sekarang saya mengerti Anda :) Ukuran tumpukan default harus optimal (lihat komentar @Lucas Trzesniewski) dan harus dibulatkan ke kelipatan terdekat dari perincian alokasi. Jika ukuran tumpukan yang ditentukan lebih besar dari ukuran tumpukan default, maka itu dibulatkan ke kelipatan terdekat yaitu 1MB. Jadi Microsoft memilih ukuran ini sebagai ukuran tumpukan default untuk semua aplikasi mode pengguna. Dan tidak ada alasan lain.
Yoh Deadfall
Ada sumber? Ada dokumentasi? :)
Nikolay Kostov
@Yoh link menarik. Anda harus meringkasnya ke dalam jawaban Anda.
Lucas Trzesniewski