当前位置: 首页 > 编程语言 > C#

C#与.NET:stackalloc分享

时间:2023-04-10 15:51:51 C#

C#与.NET:stackalloc我对stackalloc运算符函数有一些疑问。它实际上是如何分配的?我认为它执行以下操作:void*stackalloc(intsizeInBytes){void*p=StackPointer(esp);StackPointer+=sizeInBytes;if(StackPointerexceedsstacksize)thrownewStackOverflowException(...);返回p;但我做了一些测试,我不确定它是如何工作的。我们不能确切地知道它做了什么以及它是如何做的,但我想知道基础知识。我认为堆栈分配(好吧,我确实相信)比堆分配更快。那么为什么这个例子:classProgram{staticvoidMain(string[]args){Stopwatchsw1=newStopwatch();sw1.开始();堆栈分配();控制台.WriteLine(sw1.ElapsedTicks);秒表sw2=new秒表();sw2.开始();堆分配();控制台.WriteLine(sw2.ElapsedTicks);}staticunsafevoidStackAllocation(){for(inti=0;i<100;i++){int*p=stackallocint[100];}}staticvoidHeapAllocation(){for(inti=0;i<100;i++){int[]a=newint[100];}}}给出平均280~ticks的堆栈分配结果,通常1-0ticks用于堆分配?(在我的个人电脑上,IntelCorei7)。在我现在使用的计算机上(IntelCore2Duo),结果比以前的计算机更有意义(可能是因为优化代码没有在VS中检查):460~堆栈分配的滴答声,以及大约380的堆分配蜱虫。但这仍然没有意义。为什么?我认为CLR注意到我们没有使用数组,所以它可能甚至没有分配它?stackalloc更快的情况:privatestaticvolatileint_dummy;//只是为了避免任何优化//让我们测量错误的//东西。特别是因为差异//在发布版本中更明显//(在多核机器上也比单核或双核机器更明显)。staticvoidMain(string[]args){System.Diagnostics.Stopwatchsw1=newSystem.Diagnostics.Stopwatch();线程[]线程=新线程[20];sw1.开始();for(intt=0;t!=20;++t){threads[t]=newThread(DoSA);线程[t].Start();}for(intt=0;t!=20;++t)threads[t].Join();控制台.WriteLine(sw1.ElapsedTicks);System.Diagnostics.Stopwatchsw2=newSystem.Diagnostics.Stopwatch();线程=新线程[20];sw2.开始();for(intt=0;t!=20;++t){threads[t]=newThread(DoHA);线程[t].Start();for(intt=0;t!=20;++t)threads[t].Join();控制台.WriteLine(sw2.ElapsedTicks);控制台.Read();}私人的staticvoidDoSA(){Randomrnd=newRandom(1);for(inti=0;i!=100000;++i)StackAllocation(rnd);}staticunsafevoidStackAllocation(Randomrnd){intsize=rnd.Next(1024,131072);int*p=stackallocint[大小];_dummy=*(p+rnd.Next(0,size));}privatestaticvoidDoHA(){Randomrnd=newRandom(1);for(inti=0;i!=100000;++i)HeapAllocation(rnd);}staticvoidHeapAllocation(Randomrnd){intsize=rnd.Next(1024,131072);int[]a=newint[大小];_dummy=a[rnd.Next(0,size)];这段代码与问题中的代码之间的一个重要区别是:我们有几个使用堆栈分配运行的线程,它们在自己的堆栈中进行分配。使用堆分配,它们是从与其他线程共享的堆中分配的。分配的大小更大。每次分配不同的大小(尽管我播种了随机生成器以使测试更具确定性)。这使得堆碎片更容易发生,使得堆分配的效率低于每次都使用相同的分配。stackalloc,还值得注意的是,stackalloc通常用作使用fixed的替代方法来修复堆上的数组。固定数组不利于堆性能(不仅对于这段代码,对于使用同一堆的其他线程也是如此),因此如果声明的内存将被使用任何合理的时间长度,性能损失将会更大。虽然我的代码演示了stackalloc提供性能优势的情况,但问题中的问题可能更接近于大多数人可能急切地“优化”其使用的情况。希望这两段代码一起表明可以提升整体stackalloc,它也可能对性能造成太大影响。通常,您甚至不应该考虑使用stackalloc,除非您都需要使用固定内存来让stackalloc与非托管代码交互,并且应该被认为是固定的,而不是常规堆分配的替代方法。在这种情况下使用仍然需要谨慎,开始前要深思熟虑,完成后进行分析。在其他情况下使用可能会有好处,但它应该远低于您尝试的性能改进列表。编辑:回答问题的第1部分。Stackalloc在概念上与您所描述的非常相似。它获取一块堆栈内存并返回指向该块的指针。它不会像这样检查内存是否合适,但如果它试图将内存放到堆栈的末尾——创建线程时受.NET保护——那么这将导致操作系统在运行时返回异常,然后成为.NET托管异常。如果您只是在具有无限递归的方法中分配一个字节,也会发生同样的情况-除非优化调整以避免堆栈分配(有时可能),否则单个字节最终加起来足以触发堆栈溢出异常。我无法给出确切的答案,但stackalloc使用的是IL操作码localloc。我查看了发布版本的stackalloc生成的机器代码,它比我预期的要复杂。我不知道localloc是否按照您指示的那样检查堆栈大小,或者当硬件堆栈实际溢出时CPU是否检测到堆栈溢出。对此答案的评论表明提供给localloc的链接从“本地堆”分配空间。问题是除了PDF格式的实际标准之外,没有很好的MSIL在线参考。上面的链接来自System.Reflection.Emit.OpCodes类,这个类和MSIL无关,是一个生成MSIL的库。但是,在标准文档ECMA335-CommonLanguageInfrastructure中,有更精确的描述:每个方法的部分状态是本地内存池。可以使用localloc指令从本地内存池显式分配内存。在方法退出时回收本地内存池中的所有内存,这是回收本地内存池中内存的唯一方法(没有提供指令来释放在此方法调用期间分配的本地内存)。本地内存池用于在编译时分配未知类型或大小的对象,以及用于程序员不希望在托管堆中分配的对象。所以基本上“本地内存池”就是所谓的“堆栈”,C#语言使用stackalloc运算符从这个池中分配。在发布版本中,优化器足够聪明,可以完全删除对HeapAllocation的调用,从而大大减少执行时间。在使用stackalloc时执行相同的优化似乎不够聪明。如果您关闭优化或以某种方式使用分配的缓冲区,您会发现stackalloc稍微快一些。以上就是C#学习教程分享的全部内容:C#与.NET:stackalloc。如果对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: