Apa bahaya saat membuat utas dengan ukuran tumpukan 50x default?

228

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;
    }
}
Sam
sumber
98
+1. Serius. Anda bertanya apa yang terlihat seperti pertanyaan konyol dari norma dan kemudian Anda membuat kasus yang SANGAT bagus bahwa dalam skenario khusus Anda itu adalah hal yang masuk akal untuk dipertimbangkan karena Anda membuat pekerjaan rumah dan mengukur hasilnya. Ini SANGAT bagus - saya rindu itu dengan banyak pertanyaan. Sangat bagus - bagus Anda menganggap sesuatu seperti ini, sayangnya banyak programmer C # tidak menyadari peluang optimasi tersebut. Ya, sering tidak diperlukan - tetapi kadang-kadang sangat penting dan membuat perbedaan besar.
TomTom
5
Saya tertarik untuk melihat dua kode yang memiliki perbedaan 530% dalam kecepatan pemrosesan, hanya karena memindahkan array ke stack. Itu tidak terasa benar.
Dialecticus
13
Sebelum Anda melompat di jalan itu: sudahkah Anda mencoba menggunakan Marshal.AllocHGlobal(jangan lupa FreeHGlobaljuga) untuk mengalokasikan data di luar memori yang dikelola? Kemudian arahkan pointer ke a float*, dan Anda harus diurutkan.
Marc Gravell
2
Rasanya benar jika Anda melakukan banyak alokasi. Stackalloc memintas semua masalah GC yang juga dapat membuat / tidak menciptakan lokalitas yang sangat kuat pada tingkat prosesor. Ini adalah salah satu hal yang terlihat seperti optimasi mikro - kecuali jika Anda menulis program matematika kinerja tinggi dan memiliki perilaku yang tepat dan ini membuat perbedaan;)
TomTom
6
Kecurigaan saya: salah satu metode ini memicu memeriksa batas pada setiap iterasi loop sementara yang lain tidak, atau dioptimalkan pergi.
pjc50

Jawaban:

45

Setelah membandingkan kode uji dengan Sam, saya memutuskan bahwa kami berdua benar!
Namun, tentang berbagai hal:

  • Mengakses memori (membaca dan menulis) sama cepatnya di mana pun itu - tumpukan, global atau tumpukan.
  • Mengalokasikan itu, bagaimanapun, adalah tercepat pada stack dan paling lambat di heap.

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:

  1. Ketika Anda perlu membuat array sering yang tidak pernah meninggalkan fungsi (misalnya dengan melewatkan referensi), menggunakan stack akan menjadi peningkatan yang sangat besar.
  2. Jika Anda dapat mendaur ulang array, lakukan kapan saja Anda bisa! Tumpukan adalah tempat terbaik untuk penyimpanan objek jangka panjang. (Mencemari memori global tidak bagus; tumpukan frame dapat menghilang)

( 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:

Stack-allocated array time: 00:00:00.2224429
Globally-allocated array time: 00:00:00.2206767
Heap-allocated array time: 00:00:00.1842670
------------------------------------------
Fastest: Heap.

  |    S    |    G    |    H    |
--+---------+---------+---------+
S |    -    | 100.80 %| 120.72 %|
--+---------+---------+---------+
G |  99.21 %|    -    | 119.76 %|
--+---------+---------+---------+
H |  82.84 %|  83.50 %|    -    |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.

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 :

Stack-allocated array time: 00:00:00.4504903
Globally-allocated array time: 00:00:00.4020328
Heap-allocated array time: 00:00:00.3439016
------------------------------------------
Fastest: Heap.

  |    S    |    G    |    H    |
--+---------+---------+---------+
S |    -    | 112.05 %| 130.99 %|
--+---------+---------+---------+
G |  89.24 %|    -    | 116.90 %|
--+---------+---------+---------+
H |  76.34 %|  85.54 %|    -    |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.

Padahal, sepertinya stack sebenarnya semakin lambat .

Vercas
sumber
Saya harus tidak setuju, menurut hasil benchmark saya (lihat komentar di bagian bawah halaman untuk hasil) menunjukkan bahwa stack sedikit lebih cepat daripada global, dan jauh lebih cepat daripada heap; dan untuk memastikan bahwa hasil saya akurat menjalankan tes 20 kali, dan setiap metode dipanggil 100 kali per tes iterasi. Apakah Anda menjalankan benchmark dengan benar?
Sam
Saya mendapatkan hasil yang sangat tidak konsisten. Dengan kepercayaan penuh, x64, konfigurasi rilis, tanpa debugger, semuanya sama-sama cepat (perbedaan kurang dari 1%; berfluktuasi) sementara milik Anda memang jauh lebih cepat dengan tumpukan. Saya perlu menguji lebih lanjut! Sunting : Anda HARUS melempar pengecualian stack overflow. Anda hanya mengalokasikan cukup untuk array. O_o
Vercas
Ya saya tahu, sudah dekat. Anda perlu mengulangi tolok ukur beberapa kali, seperti yang saya lakukan, mungkin mencoba mengambil rata-rata lebih dari 5 berjalan.
Sam
1
@Oo Run pertama membutuhkan waktu sebanyak 100 tes untuk saya. Dari pengalaman saya, hal Java JIT ini tidak berlaku untuk. NET sama sekali. Satu-satunya "pemanasan" yang dilakukan .NET adalah memuat kelas dan rakitan ketika digunakan untuk pertama kalinya.
Vercas
2
@Vo Uji patokan saya dan yang dari intinya dia menambahkan dalam komentar untuk jawaban ini. Kumpulkan kode bersama dan jalankan beberapa ratus tes. Kemudian kembali dan laporkan kesimpulan Anda. Saya telah melakukan tes saya dengan sangat teliti, dan saya tahu betul apa yang saya bicarakan ketika mengatakan bahwa .NET tidak menginterpretasikan bytecode seperti Java, ia langsung melakukan JIT.
Vercas
28

Saya telah menemukan peningkatan kecepatan pemrosesan 530%!

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 stackallockata 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.

Hans Passant
sumber
21
Speedup 5x yang dilaporkan berasal dari pindah dari float[]ke float*. Tumpukan besar hanyalah bagaimana hal itu dilakukan. Percepatan x5 dalam beberapa skenario sepenuhnya masuk akal untuk perubahan itu.
Marc Gravell
3
Oke, saya belum memiliki cuplikan kode ketika saya mulai menjawab pertanyaan. Masih cukup dekat.
Hans Passant
22

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:

var ptr = Marshal.AllocHGlobal(sizeBytes);
try
{
    float* x = (float*)ptr;
    DoWork(x);
}
finally
{
    Marshal.FreeHGlobal(ptr);
}
Marc Gravell
sumber
1
Pertanyaan sampingan: Mengapa GC perlu memindai tumpukan? Memori yang dialokasikan oleh stackalloctidak tunduk pada pengumpulan sampah.
dcastro
6
@dcastro perlu memindai tumpukan untuk memeriksa referensi yang hanya ada di tumpukan. Saya benar-benar tidak tahu apa yang harus dilakukan ketika sampai sebesar itu 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, stackallocsangat bagus sebagai penyangga awal, tetapi untuk ruang kerja khusus, lebih baik mengalokasikan memori potongan di suatu tempat, daripada menyalahgunakan / membingungkan tumpukan,
Marc Gravell
8

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 padaThread 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.

PMF
sumber
1
Ide bagus, saya akan beralih melalui saja. Selain itu, kode saya berjalan dalam mode kepercayaan penuh, jadi apakah ada hal lain yang harus saya perhatikan?
Sam
6

Array berkinerja tinggi mungkin dapat diakses dengan cara yang sama seperti C # satu normal tetapi itu bisa menjadi awal masalah: Pertimbangkan kode berikut:

float[] someArray = new float[100]
someArray[200] = 10.0;

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:

Float* pFloat =  stackalloc float[100];
fFloat[200]= 10.0;

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.

MHOOS
sumber
Faktanya salah. Tes runtime dan compiler masih ada.
TomTom
9
@TomTom erm, tidak; jawabannya pantas; pertanyaannya berbicara tentang stackalloc, dalam hal apa kita berbicara tentang float*dll - yang tidak memiliki cek yang sama. Itu disebut unsafeuntuk alasan yang sangat bagus. Secara pribadi saya sangat senang menggunakannya unsafeketika ada alasan yang bagus, tetapi Socrates membuat beberapa poin yang masuk akal.
Marc Gravell
@ Markc Untuk kode yang ditampilkan (setelah JIT dijalankan) tidak ada lagi batas pemeriksaan karena sepele untuk kompiler dengan alasan bahwa semua akses di dalam batas. Secara umum hal ini tentu saja dapat membuat perbedaan.
Voo
6

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:

Standard       10,589.00  (1.00)
UnsafeStandard 10,612.00  (1.00)
Stackalloc     12,088.00  (1.14)
FixedStandard  10,715.00  (1.01)
GlobalAlloc    12,547.00  (1.18)

x86 JIT (ya itu masih agak sedih):

Standard       14,787.00   (1.02)
UnsafeStandard 14,549.00   (1.00)
Stackalloc     15,830.00   (1.09)
FixedStandard  14,824.00   (1.02)
GlobalAlloc    18,744.00   (1.29)

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:

public static float Standard(int size) {
    float[] samples = new float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float UnsafeStandard(int size) {
    float[] samples = new float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float Stackalloc(int size) {
    float* samples = stackalloc float[size];
    for (var ii = 0; ii < size; ii++) {
        samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
    }
    return samples[size - 1];
}

public static unsafe float FixedStandard(int size) {
    float[] prev = new float[size];
    fixed (float* samples = &prev[0]) {
        for (var ii = 0; ii < size; ii++) {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }
        return samples[size - 1];
    }
}

public static unsafe float GlobalAlloc(int size) {
    var ptr = Marshal.AllocHGlobal(size * sizeof(float));
    try {
        float* samples = (float*)ptr;
        for (var ii = 0; ii < size; ii++) {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }
        return samples[size - 1];
    } finally {
        Marshal.FreeHGlobal(ptr);
    }
}

static void Main(string[] args) {
    int inputSize = 100000;
    var results = TestSuite.Create("Tests", inputSize, Standard(inputSize)).
        Add(Standard).
        Add(UnsafeStandard).
        Add(Stackalloc).
        Add(FixedStandard).
        Add(GlobalAlloc).
        RunTests();
    results.Display(ResultColumns.NameAndIterations);
}
Voo
sumber
Pengamatan yang menarik, saya harus memeriksa tolok ukur saya lagi. Meskipun ini masih tidak benar-benar menjawab pertanyaan saya, " ... apa bahaya yang terkait dengan meningkatkan tumpukan menjadi ukuran besar ... ". Sekalipun hasil saya salah, pertanyaannya tetap valid; Namun saya menghargai upaya ini.
Sam
1
@ Sam Ketika menggunakan 12500000sebagai 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.
Voo
5

Karena perbedaan kinerja terlalu besar, masalahnya hampir tidak terkait dengan alokasi. Kemungkinan disebabkan oleh akses array.

Saya membongkar loop body fungsi:

TestMethod1:

IL_0011:  ldloc.0 
IL_0012:  ldloc.1 
IL_0013:  ldc.i4.4 
IL_0014:  mul 
IL_0015:  add 
IL_0016:  ldc.r4 32768.
IL_001b:  stind.r4 // <----------- This one
IL_001c:  ldloc.1 
IL_001d:  ldc.i4.1 
IL_001e:  add 
IL_001f:  stloc.1 
IL_0020:  ldloc.1 
IL_0021:  ldc.i4 12500000
IL_0026:  blt IL_0011

TestMethod2:

IL_0012:  ldloc.0 
IL_0013:  ldloc.1 
IL_0014:  ldc.r4 32768.
IL_0019:  stelem.r4 // <----------- This one
IL_001a:  ldloc.1 
IL_001b:  ldc.i4.1 
IL_001c:  add 
IL_001d:  stloc.1 
IL_001e:  ldloc.1 
IL_001f:  ldc.i4 12500000
IL_0024:  blt IL_0012

Kami dapat memeriksa penggunaan instruksi dan yang lebih penting, pengecualian yang mereka berikan dalam spesifikasi ECMA :

stind.r4: Store value of type float32 into memory at address

Pengecualian yang dilontarkannya:

System.NullReferenceException

Dan

stelem.r4: Replace array element at index with the float32 value on the stack.

Pengecualian yang dilemparnya:

System.NullReferenceException
System.IndexOutOfRangeException
System.ArrayTypeMismatchException

Seperti yang Anda lihat, stelemapakah 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).

HKTonyLee
sumber
4

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 TestMethod2yang 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.

    public static unsafe float TestMethod3()
    {
        float[] samples = new float[5000000];

        for (var ii = 0; ii < 5000000; ii++)
        {
            samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
        }

        return samples[5000000 - 1];
    }

    public static unsafe float TestMethod4()
    {
        float[] prev = new float[5000000];
        fixed (float* samples = &prev[0])
        {
            for (var ii = 0; ii < 5000000; ii++)
            {
                samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
            }

            return samples[5000000 - 1];
        }
    }

    public static unsafe float TestMethod5()
    {
        var ptr = Marshal.AllocHGlobal(5000000 * sizeof(float));
        try
        {
            float* samples = (float*)ptr;

            for (var ii = 0; ii < 5000000; ii++)
            {
                samples[ii] = 32768 + (ii != 0 ? samples[ii - 1] : 0);
            }

            return samples[5000000 - 1];
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
Dialecticus
sumber
Jadi M3 sama dengan M2 yang hanya ditandai dengan "tidak aman"? Agak curiga bahwa itu akan lebih cepat ... apakah Anda yakin?
Roman Starkov
@romkyns Saya baru saja menjalankan benchmark (M2 vs M3), dan yang mengejutkan M3 sebenarnya 2,14% lebih cepat dari M2.
Sam
" Kesimpulannya adalah bahwa menggunakan stack tidak diperlukan. " Ketika mengalokasikan blok besar seperti yang saya berikan di posting saya, saya setuju, tetapi, setelah baru saja menyelesaikan beberapa benchmark M1 vs M2 (menggunakan ide PFM untuk kedua metode) saya pasti akan harus tidak setuju, karena M1 sekarang 135% lebih cepat dari M2.
Sam
1
@ Sam Tapi Anda masih membandingkan akses pointer ke akses array! Yang terutama adalah apa yang membuatnya lebih cepat. TestMethod4vs TestMethod1adalah perbandingan yang jauh lebih baik untuk stackalloc.
Roman Starkov
@romkyns Ah ya bagus, saya lupa tentang itu; Saya telah menjalankan ulang tolok ukur , hanya ada perbedaan 8% sekarang (M1 menjadi yang lebih cepat dari keduanya).
Sam