stackalloc の方が速い場合:
private static volatile int _dummy; // just to avoid any optimisations
// that have us measuring the wrong
// thing. Especially since the difference
// is more noticable in a release build
// (also more noticable on a multi-core
// machine than single- or dual-core).
static void Main(string[] args)
{
System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch();
Thread[] threads = new Thread[20];
sw1.Start();
for(int t = 0; t != 20; ++t)
{
threads[t] = new Thread(DoSA);
threads[t].Start();
}
for(int t = 0; t != 20; ++t)
threads[t].Join();
Console.WriteLine(sw1.ElapsedTicks);
System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
threads = new Thread[20];
sw2.Start();
for(int t = 0; t != 20; ++t)
{
threads[t] = new Thread(DoHA);
threads[t].Start();
}
for(int t = 0; t != 20; ++t)
threads[t].Join();
Console.WriteLine(sw2.ElapsedTicks);
Console.Read();
}
private static void DoSA()
{
Random rnd = new Random(1);
for(int i = 0; i != 100000; ++i)
StackAllocation(rnd);
}
static unsafe void StackAllocation(Random rnd)
{
int size = rnd.Next(1024, 131072);
int* p = stackalloc int[size];
_dummy = *(p + rnd.Next(0, size));
}
private static void DoHA()
{
Random rnd = new Random(1);
for(int i = 0; i != 100000; ++i)
HeapAllocation(rnd);
}
static void HeapAllocation(Random rnd)
{
int size = rnd.Next(1024, 131072);
int[] a = new int[size];
_dummy = a[rnd.Next(0, size)];
}
このコードと質問のコードの重要な違い:
いくつかのスレッドが実行されています。スタック割り当てでは、独自のスタックに割り当てています。ヒープ割り当てでは、他のスレッドと共有されているヒープから割り当てています。
より大きなサイズが割り当てられます。
毎回異なるサイズが割り当てられます (テストをより決定論的にするために乱数発生器をシードしましたが)。これにより、ヒープの断片化が発生する可能性が高くなり、毎回同じ割り当てを行う場合よりもヒープ割り当ての効率が低下します。
これと同様に、を使用して配列をヒープに固定するstackalloc
代わりによく使用されることも注目に値します。fixed
配列のピニングはヒープのパフォーマンスに悪影響を及ぼします (そのコードだけでなく、同じヒープを使用する他のスレッドにとっても)。そのため、要求されたメモリが妥当な時間使用されている場合、パフォーマンスへの影響はさらに大きくなります。
私のコードstackalloc
はパフォーマンス上の利点をもたらすケースを示していますが、問題のそれはおそらく、誰かがそれを使用して熱心に「最適化」する可能性があるほとんどのケースに近いでしょう。願わくば、この 2 つのコードを組み合わせることで、全体stackalloc
が向上し、パフォーマンスが大幅に低下する可能性があることが示されます。
一般に、stackalloc
アンマネージ コードと対話するために固定メモリを使用する必要がない限り、考慮すべきではありませんfixed
。また、一般的なヒープ割り当ての代替ではなく、代替と見なす必要があります。この場合の使用には、注意が必要であり、開始する前に事前に検討し、終了後にプロファイルを作成する必要があります。
他の場合に使用すると利点が得られる可能性がありますが、試してみるパフォーマンス向上のリストのはるか下にあるはずです。
編集:
質問のパート1に答える。Stackalloc は、概念的にはあなたが説明したとおりです。スタック メモリのチャンクを取得し、そのチャンクへのポインターを返します。メモリがそのように収まるかどうかはチェックしませんが、スレッドの作成時に.NETによって保護されているスタックの最後にメモリを取得しようとすると、OSが例外をランタイムに返します。これは、.NET 管理の例外に変わります。無限再帰を使用してメソッドに 1 バイトを割り当てるだけでも、ほとんど同じことが起こります。そのスタック割り当てを回避するように呼び出しが最適化されていない限り (場合によっては可能です)、1 バイトは最終的にスタック オーバーフロー例外をトリガーするのに十分な量になります。