Penggunaan praktis dari kata kunci `stackalloc`

139

Adakah yang pernah benar-benar digunakan stackallocsaat memprogram di C #? Saya mengetahui apa itu fungsinya, tetapi satu-satunya saat itu muncul di kode saya adalah secara tidak sengaja, karena Intellisense menyarankannya ketika saya mulai mengetik static, misalnya.

Meskipun tidak terkait dengan skenario penggunaan stackalloc, saya sebenarnya melakukan interop lama dalam jumlah besar di aplikasi saya, jadi sesekali saya dapat menggunakan unsafekode. Tapi bagaimanapun saya biasanya menemukan cara untuk menghindari unsafesepenuhnya.

Dan karena ukuran tumpukan untuk satu utas di .Net adalah ~ 1Mb (perbaiki saya jika saya salah), saya bahkan lebih dilindungi dari penggunaan stackalloc.

Adakah beberapa kasus praktis di mana seseorang dapat berkata: "ini adalah jumlah data dan pemrosesan yang tepat sehingga saya tidak aman dan digunakan stackalloc"?

Groo
sumber
5
hanya memperhatikan bahwa System.Numbersmenggunakannya banyak referensiource.microsoft.com/#mscorlib/system/…
Slai

Jawaban:

160

Satu-satunya alasan untuk menggunakan stackallocadalah kinerja (baik untuk komputasi atau interop). Dengan menggunakan stackallocalih-alih array yang dialokasikan heap, Anda membuat lebih sedikit tekanan GC (GC perlu berjalan lebih sedikit), Anda tidak perlu menyematkan array ke bawah, ini lebih cepat untuk mengalokasikan daripada heap array, dan secara otomatis dibebaskan pada metode exit (array yang dialokasikan heap hanya dibatalkan alokasinya saat GC berjalan). Juga dengan menggunakan stackallocalih-alih pengalokasi asli (seperti malloc atau .Net yang setara), Anda juga mendapatkan kecepatan dan deallokasi otomatis saat keluar cakupan.

Dari segi kinerja, jika Anda menggunakan, stackallocAnda sangat meningkatkan kemungkinan cache ditemukan pada CPU karena lokalitas data.

Pop Catalin
sumber
28
Lokalitas data, bagus! Itulah yang jarang dicapai memori terkelola saat Anda ingin mengalokasikan beberapa struktur atau larik. Terima kasih!
Groo
22
Alokasi heap biasanya lebih cepat untuk objek yang dikelola daripada yang tidak dikelola karena tidak ada daftar gratis untuk dilintasi; CLR hanya menambah penunjuk heap. Sedangkan untuk lokalitas, alokasi sekuensial lebih cenderung berakhir di colocated untuk proses terkelola yang berjalan lama karena pemadatan heap.
Shea
1
"lebih cepat mengalokasikan daripada heap array" Mengapa begitu? Hanya lokalitas? Either way itu hanya sebuah pointer-benjolan, bukan?
Max Barraclough
2
@MaxBarraclough Karena Anda menambahkan biaya GC ke alokasi heap selama masa pakai aplikasi. Total alokasi biaya = alokasi + deallocation, dalam hal ini benjolan penunjuk + GC Heap, vs benjolan penunjuk + penurunan penunjuk Stack
Pop Catalin
36

Saya telah menggunakan stackalloc untuk mengalokasikan buffer untuk pekerjaan DSP [dekat] realtime. Itu adalah kasus yang sangat spesifik di mana kinerja harus sekonsisten mungkin. Perhatikan ada perbedaan antara konsistensi dan throughput keseluruhan - dalam hal ini saya tidak khawatir dengan alokasi heap yang terlalu lambat, hanya dengan non determinisme pengumpulan sampah pada saat itu dalam program. Saya tidak akan menggunakannya dalam 99% kasus.

Jim Arnold
sumber
25

stackallochanya relevan untuk kode yang tidak aman. Untuk kode terkelola, Anda tidak dapat memutuskan di mana mengalokasikan data. Jenis nilai dialokasikan di tumpukan per default (kecuali jika merupakan bagian dari jenis referensi, dalam hal ini dialokasikan di heap). Jenis referensi dialokasikan di heap.

Ukuran tumpukan default untuk aplikasi .NET vanilla biasa adalah 1 MB, tetapi Anda dapat mengubahnya di header PE. Jika Anda memulai utas secara eksplisit, Anda juga dapat menyetel ukuran yang berbeda melalui overload konstruktor. Untuk aplikasi ASP.NET ukuran tumpukan default hanya 256K, yang perlu diingat jika Anda beralih di antara dua lingkungan.

Brian Rasmussen
sumber
Apakah mungkin untuk mengubah ukuran tumpukan default dari Visual Studio?
konfigurator
@configurator: Tidak sejauh yang saya ketahui.
Brian Rasmussen
20

Stackalloc inisialisasi span. Dalam versi C # sebelumnya, hasil stackalloc hanya dapat disimpan ke dalam variabel lokal pointer. Mulai C # 7.2, stackalloc sekarang dapat digunakan sebagai bagian dari ekspresi dan dapat menargetkan rentang, dan itu dapat dilakukan tanpa menggunakan kata kunci yang tidak aman. Jadi, alih-alih menulis

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

Anda dapat menulis dengan sederhana:

Span<byte> bytes = stackalloc byte[length];

Ini juga sangat berguna dalam situasi di mana Anda memerlukan beberapa ruang awal untuk melakukan operasi, tetapi ingin menghindari mengalokasikan memori heap untuk ukuran yang relatif kecil

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Sumber: C # - All About Span: Menjelajahi Andalan .NET Baru

ant
sumber
4
Terima kasih atas tipnya. Tampaknya setiap versi baru C # semakin mendekati C ++, yang sebenarnya merupakan hal yang baik IMHO.
Groo
2
Seperti yang dapat dilihat di sini dan di sini , Spansayangnya tidak tersedia di .NET framework 4.7.2 dan bahkan tidak di 4.8 ... Sehingga fitur bahasa baru masih digunakan terbatas untuk saat ini.
Frederic
1
@Frederic: rupanya, .NET framework 4.8 adalah versi utama terakhir dari .NET framework . Versi .NET Core berikutnya akan disebut .NET 5 (tidak ada "inti" lagi, dan versi 4 dilewati untuk menghindari kebingungan dengan kerangka kerja), jadi masa depan .NET adalah .NET Core, dan sekarang terbukti bahwa .NET Framework aplikasi tidak akan mendapatkan pembaruan ini. Saya juga percaya idenya adalah untuk menghapus .NET Standard di masa depan (karena hanya akan ada ".NET" mulai saat ini).
Groo
2

Ada beberapa jawaban bagus dalam pertanyaan ini tetapi saya hanya ingin menunjukkannya

Stackalloc juga dapat digunakan untuk memanggil API asli

Banyak fungsi native mengharuskan pemanggil mengalokasikan buffer untuk mendapatkan hasil yang ditampilkan. Misalnya, fungsi CfGetPlaceholderInfo di cfapi.hmemiliki tanda tangan berikut.

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

Untuk memanggilnya di C # melalui interop,

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

Anda dapat menggunakan stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
fjch1997
sumber