首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C# & .NET: stackalloc

C# & .NET: stackalloc
EN

Stack Overflow用户
提问于 2011-12-12 10:08:18
回答 2查看 4.5K关注 0票数 23

关于stackalloc操作符的功能,我有几个问题。

  1. 它实际上是如何分配的?我以为它能做到这样: void* stackalloc(int sizeInBytes) {void= StackPointer (esp);StackPointer += sizeInBytes;if(StackPointer超过堆栈大小)抛出新StackOverflowException(.);返回p;} 但是我做了一些测试,但我不确定它是怎么工作的。我们不能确切地知道它是做什么的,也不知道它是如何做到的,但我想知道基本知识。
  2. 我认为堆栈分配比堆分配更快。那么为什么这个例子: 类程序{ static (string[] args) {秒表sw1 =新秒表();sw1.Start();StackAllocation();Console.WriteLine(sw1.ElapseTicks);秒表sw2 =新秒表();sw2.Start();HeapAllocation();Console.WriteLine(sw2.ElapseTicks);}静态不安全空StackAllocation() { for (int i= 0;i< 100;i++) { int* p= stackalloc int100;}静态空HeapAllocation() { for (int i= 0;i< 100;i++) { int[] a=新int100;}

给出了用于堆栈分配的280~ ticks的平均结果,通常给出堆分配1-0标记?(在我的个人计算机Intel i7上)。

在我现在使用的计算机(IntelCore2Duo)上,更有意义的是之前的结果(可能是因为优化代码没有签入VS):460~用于堆栈分配的滴答,以及用于堆分配的380滴答。

但这还是没道理。为什么会这样呢?我猜CLR注意到我们没有使用数组,所以可能它甚至没有分配它?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-12-12 12:13:08

stackalloc更快的情况:

代码语言:javascript
复制
 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)];
 }

这一守则与问题中的守则之间的重要区别:

  1. 我们有几个线程在运行。使用堆栈分配,它们在自己的堆栈中进行分配。对于堆分配,它们是从与其他线程共享的堆中分配的。
  2. 分配了更大的大小。
  3. 每次分配不同的大小(虽然我播种了随机生成器以使测试更具确定性)。这使得堆碎片更有可能发生,使堆分配的效率比每次分配相同的堆要低。

此外,还值得注意的是,stackalloc经常被用作使用fixed将数组固定在堆上的替代方法。固定数组不利于堆性能(不仅对于该代码,对于使用相同堆的其他线程也是如此),因此,如果声称的内存在任何合理的时间内使用,那么性能影响就会更大。

虽然我的代码演示了stackalloc给性能带来好处的情况,但在这个问题上,这个问题可能更接近于某些人可能热切地通过使用它进行“优化”的情况。希望这两段代码一起表明,整个stackalloc可以提供一个提升,它也会对性能造成很大影响。

通常,您甚至不应该考虑stackalloc,除非您需要使用固定内存与非托管代码交互,而且它应该被认为是fixed的替代,而不是一般堆分配的替代。在这种情况下,使用仍然需要谨慎,在开始之前进行预先考虑,在完成之后进行分析。

在其他情况下使用可能会带来好处,但它应该远远低于您将尝试的性能改进的列表。

编辑:

回答问题的第一部分。Stackalloc在概念上与您所描述的差不多。它获得堆栈内存块,然后返回指向该块的指针。它不检查内存是否合适,而是如果它试图将内存获取到堆栈的末尾--在线程创建时受.NET保护--那么这将导致操作系统将异常返回到运行时,然后将其转化为.NET托管异常。如果您只是在一个具有无限递归的方法中分配一个字节--除非对调用进行了优化以避免堆栈分配(有时是可能的),那么一个字节最终将足以触发堆栈溢出异常,也会发生类似的情况。

票数 11
EN

Stack Overflow用户

发布于 2011-12-12 11:18:39

  1. 我无法给出确切的答案,但stackalloc是使用IL操作码localloc实现的。我查看了stackalloc发布版本生成的机器代码,它比我预期的要复杂得多。我不知道localloc是否会按照if的指示检查堆栈大小,或者当硬件堆栈实际溢出时,CPU是否检测到堆栈溢出。 对此答案的注释表明,提供给localloc的链接分配了“本地堆”中的空间。问题是,除了PDF格式的实际标准外,没有很好的在线参考。上面的链接来自于System.Reflection.Emit.OpCodes类,它不是关于MSIL的,而是一个用于生成MSIL的库。 然而,在标准文档ECMA 335 -公共语言基础设施中有一个更精确的描述: 每个方法状态的一部分是一个本地内存池。可以使用localloc指令从本地内存池显式分配内存。本地内存池中的所有内存都是在方法退出时回收的,这是回收本地内存池内存的唯一方法(没有提供指令来释放在此方法调用期间分配的本地内存)。本地内存池用于分配在编译时类型或大小未知且程序员不希望在托管堆中分配的对象。

因此,“本地内存池”基本上就是所谓的“堆栈”,C#语言使用stackalloc运算符从这个池中分配。

  1. 在发行版构建中,优化器足够聪明地完全删除对HeapAllocation的调用,从而大大缩短了执行时间。在使用stackalloc时,执行同样的优化似乎不够聪明。如果您关闭优化或以某种方式使用分配的缓冲区,您将看到stackalloc稍微快一些。
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8472655

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档