Saat ini saya sedang mengerjakan program yang sangat kritis kinerja dan satu jalur yang saya putuskan untuk dijelajahi yang dapat membantu mengurangi konsumsi sumber daya adalah meningkatkan ukuran tumpukan pekerja saya sehingga saya dapat memindahkan sebagian besar data float[]
yang akan saya akses ke tumpukan (menggunakan stackalloc
).
Saya telah membaca bahwa ukuran tumpukan default untuk utas adalah 1 MB, jadi untuk memindahkan semua milik saya float[]
, saya harus memperluas tumpukan sekitar 50 kali (hingga 50 MB ~).
Saya mengerti ini umumnya dianggap "tidak aman" dan tidak direkomendasikan, tetapi setelah membandingkan kode saya saat ini dengan metode ini, saya telah menemukan peningkatan kecepatan pemrosesan 530% ! Jadi saya tidak bisa begitu saja melewati opsi ini tanpa penyelidikan lebih lanjut, yang membawa saya ke pertanyaan saya; apa bahaya yang terkait dengan meningkatkan tumpukan ke ukuran besar (apa yang bisa salah), dan tindakan pencegahan apa yang harus saya ambil untuk meminimalkan bahaya seperti itu?
Kode pengujian saya,
public static unsafe void TestMethod1()
{
float* samples = stackalloc float[12500000];
for (var ii = 0; ii < 12500000; ii++)
{
samples[ii] = 32768;
}
}
public static void TestMethod2()
{
var samples = new float[12500000];
for (var i = 0; i < 12500000; i++)
{
samples[i] = 32768;
}
}
sumber
Marshal.AllocHGlobal
(jangan lupaFreeHGlobal
juga) untuk mengalokasikan data di luar memori yang dikelola? Kemudian arahkan pointer ke afloat*
, dan Anda harus diurutkan.Jawaban:
Setelah membandingkan kode uji dengan Sam, saya memutuskan bahwa kami berdua benar!
Namun, tentang berbagai hal:
Bunyinya seperti ini:
stack
<global
<heap
. (alokasi waktu)Secara teknis, alokasi tumpukan tidak benar-benar alokasi, runtime hanya memastikan bagian dari tumpukan (bingkai?) dicadangkan untuk array.
Saya sangat menyarankan untuk berhati-hati dengan ini.
Saya merekomendasikan yang berikut ini:
( Catatan : 1. hanya berlaku untuk tipe nilai; tipe referensi akan dialokasikan pada heap dan manfaatnya akan dikurangi menjadi 0)
Untuk menjawab pertanyaan itu sendiri: Saya belum menemukan masalah sama sekali dengan tes tumpukan besar.
Saya percaya satu-satunya masalah yang mungkin terjadi adalah stack overflow, jika Anda tidak berhati-hati dengan panggilan fungsi dan kehabisan memori saat membuat utas Anda jika sistem hampir habis.
Bagian di bawah ini adalah jawaban awal saya. Itu salah dan tesnya tidak benar. Itu disimpan hanya untuk referensi.
Pengujian saya menunjukkan memori yang dialokasikan tumpukan dan memori global setidaknya 15% lebih lambat daripada (membutuhkan 120% waktu) memori tumpukan yang dialokasikan untuk penggunaan dalam array!
Ini adalah kode pengujian saya , dan ini adalah contoh output:
Saya menguji pada Windows 8.1 Pro (dengan Pembaruan 1), menggunakan i7 4700 MQ, di bawah .NET 4.5.1
Saya menguji keduanya dengan x86 dan x64 dan hasilnya identik.
Sunting : Saya meningkatkan ukuran tumpukan semua utas 201 MB, ukuran sampel menjadi 50 juta dan mengurangi iterasi ke 5.
Hasilnya sama seperti di atas :
Padahal, sepertinya stack sebenarnya semakin lambat .
sumber
Sejauh ini bahaya terbesar yang akan saya katakan. Ada sesuatu yang sangat salah dengan tolok ukur Anda, kode yang berperilaku tidak terduga ini biasanya memiliki bug jahat yang tersembunyi di suatu tempat.
Sangat, sangat sulit untuk mengkonsumsi banyak ruang stack dalam program .NET, selain oleh rekursi yang berlebihan. Ukuran bingkai tumpukan metode yang dikelola diatur dalam batu. Sederhananya jumlah argumen metode dan variabel lokal dalam suatu metode. Kecuali yang bisa disimpan dalam register CPU, Anda bisa mengabaikannya karena jumlahnya sangat sedikit.
Meningkatkan ukuran tumpukan tidak menghasilkan apa-apa, Anda hanya akan memesan banyak ruang alamat yang tidak akan pernah digunakan. Tidak ada mekanisme yang dapat menjelaskan peningkatan perf dari tidak menggunakan memori tentu saja.
Ini tidak seperti program asli, terutama yang ditulis dalam C, ini juga dapat menyediakan ruang untuk array pada bingkai stack. Vektor serangan malware dasar di balik tumpukan buffer overflows. Kemungkinan di C # juga, Anda harus menggunakan
stackalloc
kata kunci. Jika Anda melakukan itu maka bahaya yang jelas adalah harus menulis kode yang tidak aman yang menjadi sasaran serangan tersebut, serta korupsi bingkai tumpukan acak. Sangat sulit untuk mendiagnosis bug. Ada langkah-langkah melawan ini di kegugupan kemudian, saya pikir mulai di. NET 4.0, di mana jitter menghasilkan kode untuk meletakkan "cookie" pada bingkai tumpukan dan memeriksa apakah masih utuh ketika metode kembali. Kerusakan instan pada desktop tanpa ada cara untuk mencegat atau melaporkan kecelakaan jika itu terjadi. Itu ... berbahaya bagi kondisi mental pengguna.Utas utama program Anda, yang dimulai oleh sistem operasi, akan memiliki tumpukan 1 MB secara default, 4 MB ketika Anda mengkompilasi program Anda yang menargetkan x64. Semakin banyak yang membutuhkan menjalankan Editbin.exe dengan opsi / STACK di acara post build. Anda biasanya dapat meminta hingga 500 MB sebelum program Anda mengalami kesulitan untuk memulai ketika berjalan dalam mode 32-bit. Utas juga, tentu saja, jauh lebih mudah, zona bahaya biasanya berkisar sekitar 90 MB untuk program 32-bit. Dipicu ketika program Anda telah berjalan untuk waktu yang lama dan ruang alamat terfragmentasi dari alokasi sebelumnya. Penggunaan ruang alamat total harus sudah tinggi, lebih dari satu pertunjukan, untuk mendapatkan mode kegagalan ini.
Periksa ulang kode Anda, ada sesuatu yang sangat salah. Anda tidak bisa mendapatkan speedup x5 dengan tumpukan yang lebih besar kecuali Anda secara eksplisit menulis kode Anda untuk memanfaatkannya. Yang selalu membutuhkan kode yang tidak aman. Menggunakan pointer di C # selalu memiliki kemampuan untuk membuat kode lebih cepat, itu tidak dikenakan pemeriksaan batas array.
sumber
float[]
kefloat*
. Tumpukan besar hanyalah bagaimana hal itu dilakukan. Percepatan x5 dalam beberapa skenario sepenuhnya masuk akal untuk perubahan itu.Saya akan memiliki reservasi di sana yang saya tidak akan tahu bagaimana memperkirakannya - izin, GC (yang perlu memindai tumpukan), dll - semua bisa terpengaruh. Saya akan sangat tergoda untuk menggunakan memori yang tidak dikelola sebagai gantinya:
sumber
stackalloc
tidak tunduk pada pengumpulan sampah.stackalloc
- itu agak perlu untuk melompat, dan Anda berharap itu akan melakukannya dengan mudah - tetapi intinya saya mencoba untuk membuat adalah bahwa ia memperkenalkan komplikasi / masalah yang tidak perlu . IMO,stackalloc
sangat bagus sebagai penyangga awal, tetapi untuk ruang kerja khusus, lebih baik mengalokasikan memori potongan di suatu tempat, daripada menyalahgunakan / membingungkan tumpukan,Satu hal yang bisa salah adalah Anda mungkin tidak mendapatkan izin untuk melakukannya. Kecuali berjalan dalam mode kepercayaan penuh, Kerangka ini hanya akan mengabaikan permintaan untuk ukuran tumpukan yang lebih besar (lihat MSDN pada
Thread Constructor (ParameterizedThreadStart, Int32)
)Alih-alih meningkatkan ukuran tumpukan sistem ke angka besar, saya akan menyarankan untuk menulis ulang kode Anda sehingga menggunakan Iterasi dan implementasi tumpukan manual di heap.
sumber
Array berkinerja tinggi mungkin dapat diakses dengan cara yang sama seperti C # satu normal tetapi itu bisa menjadi awal masalah: Pertimbangkan kode berikut:
Anda mengharapkan pengecualian di luar batas dan ini sepenuhnya masuk akal karena Anda mencoba mengakses elemen 200 tetapi nilai maksimum yang dibolehkan adalah 99. Jika Anda pergi ke rute stackalloc maka tidak akan ada objek yang melilit array Anda untuk terikat cek dan berikut ini tidak akan menunjukkan pengecualian:
Di atas Anda mengalokasikan cukup memori untuk menampung 100 float dan Anda sedang mengatur ukuran lokasi memori (float) yang dimulai pada lokasi dimulai dari memori ini + 200 * sizeof (float) untuk menyimpan nilai float Anda 10. Tidak mengherankan memori ini berada di luar memori yang dialokasikan untuk pelampung dan tidak ada yang akan tahu apa yang bisa disimpan di alamat itu. Jika Anda beruntung, Anda mungkin telah menggunakan beberapa memori yang saat ini tidak digunakan tetapi pada saat yang sama kemungkinan Anda mungkin menimpa beberapa lokasi yang digunakan untuk menyimpan variabel lain. Untuk Meringkas: Perilaku runtime yang tidak terduga.
sumber
stackalloc
, dalam hal apa kita berbicara tentangfloat*
dll - yang tidak memiliki cek yang sama. Itu disebutunsafe
untuk alasan yang sangat bagus. Secara pribadi saya sangat senang menggunakannyaunsafe
ketika ada alasan yang bagus, tetapi Socrates membuat beberapa poin yang masuk akal.Bahasa Microbenchmarking dengan JIT dan GC seperti Java atau C # bisa sedikit rumit, jadi umumnya ide yang baik untuk menggunakan kerangka kerja yang ada - Java menawarkan mhf atau Caliper yang sangat bagus, sayangnya sejauh pengetahuan saya C # tidak menawarkan apa pun yang mendekati itu. Jon Skeet menulis ini sini yang saya anggap secara membabi buta akan mengurus hal-hal paling penting (Jon tahu apa yang dia lakukan di area itu; juga ya jangan khawatir saya benar-benar memeriksa). Saya mengubah pengaturan waktunya sedikit karena 30 detik per tes setelah pemanasan terlalu banyak untuk kesabaran saya (5 detik harus dilakukan).
Jadi pertama hasilnya, .NET 4.5.1 di bawah Windows 7 x64 - angka menunjukkan iterasi yang dapat dijalankan dalam 5 detik sehingga lebih tinggi lebih baik.
x64 JIT:
x86 JIT (ya itu masih agak sedih):
Ini memberikan percepatan yang jauh lebih masuk akal, paling banyak 14% (dan sebagian besar dari overhead adalah karena GC harus berjalan, menganggapnya skenario terburuk yang realistis). Hasil x86 menarik - tidak sepenuhnya jelas apa yang terjadi di sana.
dan inilah kodenya:
sumber
12500000
sebagai ukuran saya benar-benar mendapatkan pengecualian stackoverflow. Tetapi sebagian besar ini adalah tentang menolak premis yang mendasari bahwa menggunakan kode yang dialokasikan stack adalah beberapa urutan besarnya lebih cepat. Kami melakukan cukup banyak pekerjaan paling sedikit yang mungkin di sini jika tidak dan perbedaannya sudah hanya sekitar 10-15% - dalam praktiknya akan lebih rendah .. ini menurut saya pasti mengubah seluruh diskusi.Karena perbedaan kinerja terlalu besar, masalahnya hampir tidak terkait dengan alokasi. Kemungkinan disebabkan oleh akses array.
Saya membongkar loop body fungsi:
TestMethod1:
TestMethod2:
Kami dapat memeriksa penggunaan instruksi dan yang lebih penting, pengecualian yang mereka berikan dalam spesifikasi ECMA :
Pengecualian yang dilontarkannya:
Dan
Pengecualian yang dilemparnya:
Seperti yang Anda lihat,
stelem
apakah lebih banyak bekerja dalam pemeriksaan rentang array dan ketik. Karena badan loop melakukan hal kecil (hanya menetapkan nilai), overhead pemeriksaan mendominasi waktu perhitungan. Jadi itu sebabnya kinerjanya berbeda sebesar 530%.Dan ini juga menjawab pertanyaan Anda: bahayanya adalah tidak adanya rentang array & pengecekan tipe. Ini tidak aman (sebagaimana disebutkan dalam deklarasi fungsi; D).
sumber
EDIT: (perubahan kecil dalam kode dan dalam pengukuran menghasilkan perubahan besar dalam hasilnya)
Pertama saya menjalankan kode yang dioptimalkan dalam debugger (F5) tapi itu salah. Ini harus dijalankan tanpa debugger (Ctrl + F5). Kedua, kode mungkin dioptimalkan secara menyeluruh, jadi kita harus membuatnya rumit sehingga pengoptimal tidak mengacaukan pengukuran kita. Saya membuat semua metode mengembalikan item terakhir dalam array, dan array diisi secara berbeda. Juga ada nol ekstra di OP
TestMethod2
yang selalu membuatnya sepuluh kali lebih lambat.Saya mencoba beberapa metode lain, selain dua yang Anda berikan. Metode 3 memiliki kode yang sama dengan metode 2 Anda, tetapi fungsinya dinyatakan
unsafe
. Metode 4 menggunakan akses pointer ke array yang dibuat secara teratur. Metode 5 menggunakan akses pointer ke memori yang tidak dikelola, seperti yang dijelaskan oleh Marc Gravell. Kelima metode berjalan dalam waktu yang sangat mirip. M5 adalah yang tercepat (dan M1 adalah yang kedua). Perbedaan antara yang tercepat dan paling lambat adalah sekitar 5%, yang bukan sesuatu yang saya pedulikan.sumber
TestMethod4
vsTestMethod1
adalah perbandingan yang jauh lebih baik untukstackalloc
.