关于stackalloc操作符的功能,我有几个问题。
给出了用于堆栈分配的280~ ticks的平均结果,通常给出堆分配的1-0标记?(在我的个人计算机Intel i7上)。
在我现在使用的计算机(IntelCore2Duo)上,更有意义的是之前的结果(可能是因为优化代码没有签入VS):460~用于堆栈分配的滴答,以及用于堆分配的380滴答。
但这还是没道理。为什么会这样呢?我猜CLR注意到我们没有使用数组,所以可能它甚至没有分配它?
发布于 2011-12-12 12:13:08
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给性能带来好处的情况,但在这个问题上,这个问题可能更接近于某些人可能热切地通过使用它进行“优化”的情况。希望这两段代码一起表明,整个stackalloc可以提供一个提升,它也会对性能造成很大影响。
通常,您甚至不应该考虑stackalloc,除非您需要使用固定内存与非托管代码交互,而且它应该被认为是fixed的替代,而不是一般堆分配的替代。在这种情况下,使用仍然需要谨慎,在开始之前进行预先考虑,在完成之后进行分析。
在其他情况下使用可能会带来好处,但它应该远远低于您将尝试的性能改进的列表。
编辑:
回答问题的第一部分。Stackalloc在概念上与您所描述的差不多。它获得堆栈内存块,然后返回指向该块的指针。它不检查内存是否合适,而是如果它试图将内存获取到堆栈的末尾--在线程创建时受.NET保护--那么这将导致操作系统将异常返回到运行时,然后将其转化为.NET托管异常。如果您只是在一个具有无限递归的方法中分配一个字节--除非对调用进行了优化以避免堆栈分配(有时是可能的),那么一个字节最终将足以触发堆栈溢出异常,也会发生类似的情况。
发布于 2011-12-12 11:18:39
stackalloc是使用IL操作码localloc实现的。我查看了stackalloc发布版本生成的机器代码,它比我预期的要复杂得多。我不知道localloc是否会按照if的指示检查堆栈大小,或者当硬件堆栈实际溢出时,CPU是否检测到堆栈溢出。
对此答案的注释表明,提供给localloc的链接分配了“本地堆”中的空间。问题是,除了PDF格式的实际标准外,没有很好的在线参考。上面的链接来自于System.Reflection.Emit.OpCodes类,它不是关于MSIL的,而是一个用于生成MSIL的库。
然而,在标准文档ECMA 335 -公共语言基础设施中有一个更精确的描述:
每个方法状态的一部分是一个本地内存池。可以使用localloc指令从本地内存池显式分配内存。本地内存池中的所有内存都是在方法退出时回收的,这是回收本地内存池内存的唯一方法(没有提供指令来释放在此方法调用期间分配的本地内存)。本地内存池用于分配在编译时类型或大小未知且程序员不希望在托管堆中分配的对象。因此,“本地内存池”基本上就是所谓的“堆栈”,C#语言使用stackalloc运算符从这个池中分配。
HeapAllocation的调用,从而大大缩短了执行时间。在使用stackalloc时,执行同样的优化似乎不够聪明。如果您关闭优化或以某种方式使用分配的缓冲区,您将看到stackalloc稍微快一些。https://stackoverflow.com/questions/8472655
复制相似问题