Yığın boyutu varsayılan 50x olan bir iş parçacığı oluşturmanın tehlikeleri nelerdir?


228

Şu anda çok performans açısından kritik bir program üzerinde çalışıyorum ve kaynak tüketimini azaltmaya yardımcı olabilecek keşfetmeye karar verdiğim bir yol, iş parçacığımın yığın boyutunu artırıyordu, böylece float[]erişeceğim verilerin çoğunu taşıyabilirim yığını (kullanarak stackalloc).

Ben ettik okumak bir iş parçacığı için varsayılan yığın boyutu böylece tüm hareket ettirmek için, 1 MB olduğunu float[]ben (50 MB ~ kadar) yaklaşık 50 kat yığını genişletmek zorunda kalacak s.

Bunun genellikle "güvensiz" olarak kabul edildiğini ve önerilmediğini anlıyorum, ancak mevcut kodumu bu yönteme göre karşılaştırdıktan sonra , işlem hızında % 530 artış olduğunu keşfettim ! Bu yüzden daha fazla araştırma yapmadan bu seçeneği geçemiyorum, bu da beni soruma götürüyor; yığını bu kadar büyük bir boyuta yükseltmenin getirdiği tehlikeler nelerdir (yanlış gidebilir) ve bu tür tehlikeleri en aza indirmek için ne gibi önlemler almalıyım?

Test kodum,

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;
    }
}

98
+1. Ciddi anlamda. KİLİTLERİN normdan aptalca bir soru gibi olduğunu sorarsınız ve sonra kendi senaryosunda dikkate alınması mantıklı bir şey olduğu ÇOK iyi bir durum yaparsınız, çünkü ödevinizi yaptınız ve sonucu ölçtünüz. Bu ÇOK iyi - birçok soru ile özledim. Çok güzel - iyi böyle bir şey düşünün, ne yazık ki birçok C # programcı bu optimizasyon fırsatlarının farkında değilsiniz. Evet, çoğu zaman gerekli değildir - ancak bazen kritiktir ve büyük bir fark yaratır.
TomTom

5
Sadece yığın dizisine taşıma nedeniyle, işlem hızı% 530 fark olan iki kodu görmek ilgimi çekiyor. Bu doğru gelmiyor.
Dialecticus

13
Bu yolu atlamadan önce: verileri yönetilen belleğin dışına tahsis etmek Marshal.AllocHGlobaliçin kullanmayı da denediniz mi? Ardından işaretçiyi a üzerine getirdiğinizde sıralamanız gerekir. FreeHGlobalfloat*
Marc Gravell

2
Çok fazla tahsis yaparsanız doğru hissedersiniz. Stackalloc, işlemci düzeyinde çok güçlü bir konum oluşturabilen / oluşturan tüm GC sorunlarını atlar. Bu, mikro optimizasyonlara benzeyen şapkalardan biridir - yüksek performanslı bir matematik programı yazmazsanız ve tam olarak bu davranışa sahip değilseniz ve bir fark
TomTom

6
Şüphem: Bu yöntemlerden biri, her döngü yinelemesinde diğeri yapmazken sınır denetimini tetikler veya optimize edilir.
pjc50

Yanıtlar:


45

Test kodunu Sam ile karşılaştırdığımızda ikimizin de haklı olduğunu belirledim!
Ancak, farklı şeyler hakkında:

  • Belleğe (okuma ve yazma) erişmek her yerde olduğu kadar hızlıdır - yığın, genel veya yığın.
  • Ayrılacak bunu, ancak, öbek üzerinde en yavaş yığın en hızlı ve.

Böyle giderse: stack< global< heap. (ayırma zamanı)
Teknik olarak, yığın ayırma gerçekten bir ayırma değildir, çalışma zamanı yığının bir kısmının (kare?) dizi için ayrıldığından emin olur.

Yine de buna dikkat etmenizi şiddetle tavsiye ediyorum.
Aşağıdakileri tavsiye ederim:

  1. İşlevi asla terk etmeyen (örneğin referansını geçerek) dizileri sık sık oluşturmanız gerektiğinde , yığını kullanmak çok büyük bir gelişme olacaktır.
  2. Bir diziyi geri dönüştürebilirseniz, bunu istediğiniz zaman yapın! Yığın, uzun süreli nesne depolama için en iyi yerdir. (küresel belleği kirletmek hoş değildir; yığın kareleri kaybolabilir)

( Not : 1. sadece değer türleri için geçerlidir; referans türleri öbek üzerinde tahsis edilecek ve fayda 0'a düşürülecektir)

Sorunun kendisini cevaplamak için: Herhangi bir büyük yığın testi ile herhangi bir sorunla karşılaşmadım.
Ben sadece olası sorunları bir yığın taşması olduğuna inanıyorum, eğer fonksiyon çağrıları ve sistem zayıf çalışıyorsa iş parçacığı (ler) oluştururken bellek yetersiz olduğunda dikkatli değilseniz.

Aşağıdaki bölüm ilk cevabım. Yanlış-ish ve testler doğru değil. Sadece referans olarak saklanır.


Testim, yığına ayrılan belleği ve genel belleğin dizilerde kullanım için yığınla ayrılmış belleğe göre en az% 15 daha yavaş olduğunu gösteriyor!

Bu benim test kodum ve bu örnek bir çıktı:

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.

.NET 4.5.1 altında i7 4700 MQ kullanarak Windows 8.1 Pro'da (Güncelleme 1 ile)
test ettim. Hem x86 hem de x64 ile test ettim ve sonuçlar aynı.

Düzenleme : Tüm iş parçacıklarının yığın boyutunu 201 MB, örnek boyutunu 50 milyona çıkardım ve yinelemeleri 5'e düşürdüm
. Sonuçlar yukarıdakiyle aynıdır :

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.

Yine de, yığın aslında yavaşlıyor gibi görünüyor .


Ben göre, katılmıyorum olurdu benim kıyaslama sonuçlarına (sonuçlar için sayfanın altındaki Açıklama bakınız) göstermek yığını çok daha hızlı yığın daha marjinal hızlı küresel daha olduğunu ve var; ve sonuçlarımın doğru olduğundan emin olmak için testi 20 kez yürüttük ve her yönteme test yinelemesi başına 100 kez çağrıldı. Karşılaştırmanızı kesinlikle doğru mu yapıyorsunuz?
Sam

Çok tutarsız sonuçlar alıyorum. Tam güven, x64, sürüm yapılandırması, hata ayıklayıcı yok, hepsi eşit derecede hızlı (% 1'den az fark; dalgalanıyor), bir yığınla gerçekten çok daha hızlı. Daha fazla teste ihtiyacım var! Düzenleme : Sevgiler bir yığın taşma istisnası atmalısınız. Sadece dizi için yeterince ayırırsınız. O_o
Vercas

Evet biliyorum, yakın. Karşılaştırmaları birkaç kez tekrarlamanız gerekiyor, benim yaptığım gibi belki de ortalama 5'ten fazla koşmayı deneyin.
Sam

1
@Voo 1. tur benim için herhangi bir testin 100. turu kadar zaman aldı. Benim deneyimime göre, bu Java JIT şey .NET için hiç geçerli değildir. .NET'in yaptığı tek "ısınma", ilk kez kullanıldığında sınıfları ve derlemeleri yüklemektir.
Vercas

2
@Voo Karşılaştırmamı ve bu cevaba bir yorumda eklediği özü test edin. Kodları bir araya getirin ve birkaç yüz test yapın. Sonra geri dönün ve sonucunuzu bildirin. Testlerimi çok ayrıntılı bir şekilde yaptım ve .NET'in Java gibi herhangi bir bayt kodunu yorumlamadığını söylerken ne hakkında konuştuğumu çok iyi biliyorum, anında JITs.
Vercas

28

İşleme hızında% 530 artış olduğunu keşfettim!

Bu söyleyebileceğim en büyük tehlike. Karşılaştırmanızda ciddi bir yanlışlık var, bu öngörülmeyen bir şekilde davranan kod genellikle bir yerde gizli bir hataya sahiptir.

Aşırı özyineleme dışında, bir .NET programında çok fazla yığın alanı kullanmak çok, çok zordur. Yönetilen yöntemlerin yığın çerçevesinin boyutu taş olarak ayarlanır. Sadece yöntemin argümanlarının ve bir yöntemin yerel değişkenlerinin toplamı. Bir CPU kaydında saklanabilenler eksi, çok az olduğu için bunu göz ardı edebilirsiniz.

Yığın boyutunu artırmak hiçbir şey yapmaz, sadece asla kullanılmayacak bir grup adres alanı ayırırsınız. Tabii ki hafızayı kullanmamanın mükemmel bir artışını açıklayabilecek bir mekanizma yok.

Bu, yerel bir programdan farklı olarak, özellikle C ile yazılmış bir programdır, ayrıca yığın çerçevesindeki diziler için de yer ayırabilir. Yığın arabelleğinin arkasındaki temel kötü amaçlı yazılım saldırısı vektörü. C # 'da da mümkündür, stackallocanahtar kelimeyi kullanmanız gerekir . Bunu yapıyorsanız, bariz tehlike, bu tür saldırılara maruz kalan güvenli olmayan kodların yanı sıra rastgele yığın çerçeve bozulması yazmak zorunda kalıyor. Hataları teşhis etmek çok zor. Daha sonra jitters buna karşı bir karşı önlem var, ben jitter yığın çerçeve üzerinde bir "çerez" koymak için kod üretir ve yöntem döndüğünde hala sağlam olup olmadığını denetler. Bu olursa, aksilikleri durdurmak veya bildirmek için herhangi bir yol yapmadan masaüstüne anında çökme. Bu ... kullanıcının zihinsel durumu için tehlikelidir.

Programınızın ana iş parçacığı, işletim sistemi tarafından başlatılan, varsayılan olarak 1 MB yığın, program hedefleme x64 derlediğinizde 4 MB yığın olacaktır. Bir post build olayında / STACK seçeneğiyle Editbin.exe dosyasını çalıştırmayı gerektiren artış. Programınızın 32 bit modunda çalışırken başlaması konusunda sorun yaşamadan önce 500 MB'a kadar bellek isteyebilirsiniz. Tehlikeler de elbette çok daha kolay olabilir, tehlike bölgesi genellikle 32 bit bir program için 90 MB civarında gezer. Programınız uzun süre çalıştığında ve adres alanı önceki ayırmalardan parçalandığında tetiklenir. Bu hata modunu elde etmek için toplam adres alanı kullanımı zaten bir konser üzerinden yüksek olmalıdır.

Kodunuzu üç kez kontrol edin, çok yanlış bir şey var. Kodunuzdan yararlanmak için açıkça kod yazmazsanız, daha büyük bir yığınla x5 hızlandırması alamazsınız. Bu her zaman güvenli olmayan kod gerektirir. C # işaretçileri kullanmak her zaman daha hızlı kod oluşturmak için bir ustalık vardır, dizi sınır denetimlerine tabi değildir.


21
Bildirilen 5x hızlan, 'dan' float[]e geçiyordu float*. Büyük yığın, bunun nasıl gerçekleştirildiğiydi. Bazı senaryolarda x5 hızlanması bu değişiklik için tamamen makul.
Marc Gravell

3
Tamam, soruyu cevaplamaya başladığımda henüz kod pasajı yoktu. Hala yeterince yakın.
Hans Passant

22

Orada sadece nasıl tahmin - bilmiyordum bir rezervasyon olurdu - izinler, GC (hangi yığın tarama gerekir), vb - tüm etkilenmiş olabilir. Bunun yerine yönetilmeyen belleği kullanmak çok cazip olurdu:

var ptr = Marshal.AllocHGlobal(sizeBytes);
try
{
    float* x = (float*)ptr;
    DoWork(x);
}
finally
{
    Marshal.FreeHGlobal(ptr);
}

1
Yan soru: GC'nin yığını neden taraması gerekiyor? Tarafından ayrılan bellek stackallocçöp toplamaya tabi değildir.
dcastro

6
@dcastro, yalnızca yığın üzerinde var olan referansları kontrol etmek için yığını taraması gerekir. Böyle büyük bir şey olduğunda ne yapacağını bilmiyorum stackalloc- biraz atlaması gerekiyor ve bunu zahmetsizce yapmasını umuyorsunuz - ama yapmaya çalıştığım nokta, gereksiz komplikasyonlar / endişeler. IMO, stackallocbir çizik tamponu olarak harika, ancak özel bir çalışma alanı için, yığını kötüye kullanmak / karıştırmak yerine, bir yere sadece bir yığın bellek ayırması daha bekleniyor
Marc Gravell

8

Yanlış gidebilecek şeylerden biri, bunu yapma izniniz olmayabilir. Tam güven modunda çalışmadığı sürece, Çerçeve yalnızca daha büyük bir yığın boyutu isteğini yoksayar (bkz. MSDN on Thread Constructor (ParameterizedThreadStart, Int32))

Sistem yığını boyutunu bu kadar büyük sayılara yükseltmek yerine, kodunuzu yeniden yazmanızı ve böylece yığın üzerinde Iteration ve manuel yığın uygulamasını kullanmasını öneririm.


1
İyi fikir, bunun yerine yineleyeceğim. Bunun yanı sıra, kodum tam güven modunda çalışıyor, bu yüzden dikkat etmem gereken başka şeyler var mı?
Sam

6

Yüksek performanslı dizilere normal bir C # ile aynı şekilde erişilebilir, ancak bu sorunun başlangıcı olabilir: Aşağıdaki kodu düşünün:

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

Sınır dışı bir istisna beklersiniz ve bu tamamen mantıklıdır, çünkü 200 elemanına erişmeye çalışıyorsunuz ancak izin verilen maksimum değer 99'dur. Eğer stackalloc yoluna giderseniz, bağlı kontrole dizinizin etrafına sarılmış hiçbir nesne olmayacaktır. aşağıdakiler istisna göstermez:

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

Yukarıda 100 şamandıra için yeterli bellek ayırıyorsunuz ve bu belleğin başladığı yerde başlayan sizeof (float) bellek konumunu + float değerinizi 10 tutmak için 200 * sizeof (float) ayarlıyorsunuz. Şaşırtıcı olmayan bir şekilde bu bellek şamandıralar için bellek ayırdı ve kimse bu adreste ne saklanabileceğini bilemezdi. Şanslıysanız, şu anda kullanılmamış bazı bellekler kullanmış olabilirsiniz, ancak aynı zamanda diğer değişkenleri saklamak için kullanılan bir konumun üzerine yazabilirsiniz. Özetlemek gerekirse: Öngörülemeyen çalışma zamanı davranışı.


Aslında yanlış. Çalışma zamanı ve derleyici testleri hala oradadır.
TomTom

9
@TomTom erm, hayır; cevabın değeri vardır; sorudan bahsediyor stackalloc, bu durumda konuşuyoruz float*vb. - aynı kontrollere sahip değil. unsafeÇok iyi bir nedenden dolayı çağrılır . Şahsen unsafeiyi bir neden olduğunda kullanmaktan çok mutluyum , ancak Sokrates bazı makul noktalar ortaya koyuyor.
Marc Gravell

@Marc Gösterilen kod için (JIT çalıştırıldıktan sonra) artık sınır denetimi yoktur, çünkü derleyicinin tüm erişimlerin sınırlar içinde olmasını sağlaması önemsizdir. Genel olarak bu kesinlikle bir fark yaratabilir.
Voo

6

Java veya C # gibi JIT ve GC ile mikro karşılaştırmalı diller biraz karmaşık olabilir, bu nedenle mevcut bir çerçeveyi kullanmak genellikle iyi bir fikirdir - Java, ne yazık ki bilgimden en iyi şekilde C # sunmayan mhf veya Kaliper sunar bunlara yaklaşan herhangi bir şey. Jon Skeet yazdığı bu körü körüne en önemli şeylerden ilgilenir varsayıyoruz ki burada (Jon o alanda ne yaptığını bilir; ayrıca evet hayır ben aslında kontrol etti endişelendiren). Isınmadan sonra test başına 30 saniye sabrım için çok fazla olduğu için zamanlamayı biraz değiştirdim (5 saniye yapmalı).

İlk olarak, Windows 7 x64 altında .NET 4.5.1 - sayılar, 5 saniye içinde çalıştırabileceği yinelemeleri gösterir, böylece daha yüksek olur.

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 (evet bu hala üzücü):

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)

Bu, en fazla% 14'lük çok daha makul bir hızlanma sağlar (ve genel giderlerin çoğu GC'nin çalışması gerektiğinden, bunu en kötü durum senaryosunu gerçekçi olarak düşünün). Ancak x86 sonuçları ilginç - orada neler olduğunu tam olarak belli değil.

ve işte kod:

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);
}

İlginç bir gözlem, testlerimi tekrar kontrol etmem gerekecek. Bu hala soruma gerçekten cevap vermese de, " ... yığını bu kadar büyük bir boyuta yükseltmenin getirdiği tehlikeler nelerdir ... ". Sonuçlarım yanlış olsa bile, soru hala geçerlidir; Yine de çabayı takdir ediyorum.
Sam

1
@ 12500000Boyut olarak kullanırken aslında bir stackoverflow istisna alıyorum. Ancak çoğunlukla, yığın tahsisli kod kullanmanın birkaç büyüklük sırası daha hızlı olduğu temel dayanak noktasını reddetmekle ilgiliydi. Aksi takdirde burada mümkün olan en az işi yapıyoruz ve fark zaten sadece% 10-15'tir - pratikte daha da düşük olacaktır .. bence bu tartışmayı kesinlikle değiştiriyor.
Voo

5

Performans farkı çok büyük olduğundan, sorun tahsisle neredeyse hiç ilgili değildir. Büyük olasılıkla dizi erişiminden kaynaklanır.

Fonksiyonların döngü gövdesini söktüm:

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

Talimatın kullanımını ve daha da önemlisi, ECMA spesifikasyonlarında yaptıkları istisnaları kontrol edebiliriz :

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

Attığı istisnalar:

System.NullReferenceException

Ve

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

İstisna hariç:

System.NullReferenceException
System.IndexOutOfRangeException
System.ArrayTypeMismatchException

Gördüğünüz gibi stelemdizi aralığı kontrolünde ve tür kontrolünde daha çok iş yapıyor. Döngü gövdesi çok az şey yaptığından (yalnızca değer atama), denetimin ek yükü hesaplama süresine hakim olur. Bu yüzden performans% 530 oranında farklılık gösteriyor.

Ve bu da sorularınızı cevaplıyor: Tehlike dizi aralığı ve tip kontrolünün olmamasıdır. Bu güvensizdir (fonksiyon bildiriminde belirtildiği gibi; D).


4

EDIT: (kodda ve ölçümde küçük değişiklik, sonuçta büyük değişiklik yaratır)

Öncelikle hata ayıklayıcıda (F5) optimize edilmiş kodu çalıştırdım ama bu yanlıştı. Hata ayıklayıcı olmadan çalıştırılmalıdır (Ctrl + F5). İkincisi, kod iyice optimize edilmiş olabilir, bu nedenle optimize edicinin ölçümümüzle uğraşmaması için karmaşık hale getirmeliyiz. Tüm yöntemlerin dizideki son öğeyi döndürmesini sağladım ve dizi farklı şekilde dolduruldu. Ayrıca OP'lerde TestMethod2her zaman on kat daha yavaş yapan ekstra bir sıfır var .

Sağladığınız ikisine ek olarak başka yöntemler denedim. Yöntem 3, yöntem 2 ile aynı koda sahiptir, ancak işlev bildirildi unsafe. Yöntem 4, düzenli olarak oluşturulan diziye işaretçi erişimini kullanmaktır. Yöntem 5, Marc Gravell tarafından tarif edildiği gibi yönetilmeyen belleğe işaretçi erişimini kullanmaktır. Beş yöntemin hepsi çok benzer zamanlarda çalışır. M5 en hızlısıdır (ve M1 ikinci sıradadır). En hızlı ve en yavaş arasındaki fark yaklaşık% 5'tir, bu da umursadığım bir şey değildir.

    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);
        }
    }

M3 sadece "güvensiz" ile işaretlenmiş M2 ile aynı mı? Daha hızlı olacağından şüpheleniyorum ... emin misiniz?
Roman Starkov

@romkyns Bir kıyaslama yaptım (M2 vs M3) ve şaşırtıcı bir şekilde M3 aslında M2'den% 2.14 daha hızlı.
Sam

" Sonuç, yığının kullanılmasına gerek olmadığıdır. " Yazımda verdiğim gibi büyük bloklar tahsis ederken katılıyorum, ancak, M1 vs M2 ile ilgili bazı kriterleri tamamladıktan sonra ( her iki yöntem için PFM fikrini kullanarak ) kesinlikle M1 artık M2'den% 135 daha hızlı olduğu için katılmıyorum.
Sam

1
@Sam Ama yine de dizi erişimiyle işaretçi erişimini karşılaştırıyorsunuz! Bu öncelikle onu daha hızlı yapan şeydir. TestMethod4vs TestMethod1için çok daha iyi bir karşılaştırma stackalloc.
Roman Starkov

@romkyns Ah evet iyi bir nokta, bunu unuttum; Ben yeniden ran ettik kriterler , (M1 hızlı iki olmak üzere) artık yalnızca bir% 8 fark var.
Sam
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.