在 C# 中检查堆栈大小 [英] Checking stack size in C#

查看:61
本文介绍了在 C# 中检查堆栈大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法在 C# 中检查线程堆栈大小?

Is there a way to check threads stack size in C#?

推荐答案

这是非要问,买不起(Raymond Chen先说了.)如果代码依赖于有足够的堆栈空间到它有的程度首先检查一下,可能值得重构它以使用显式 Stack<T> 对象.John 关于使用分析器的评论有其优点.

This is a case of if you have to ask, you can't afford it (Raymond Chen said it first.) If the code depends on there being enough stack space to the extent that it has to check first, it might be worthwhile to refactor it to use an explicit Stack<T> object instead. There's merit in John's comment about using a profiler instead.

也就是说,事实证明有一种方法可以估计剩余的堆栈空间.这并不精确,但对于评估您离底部的距离已经足够有用了.以下内容主要基于 Joe Duffy 的一篇优秀文章.

That said, it turns out that there is a way to estimate the remaining stack space. It's not precise, but it's useful enough for the purpose of evaluating how close to the bottom you are. The following is heavily based on an excellent article by Joe Duffy.

我们知道(或将做出假设):

We know (or will make the assumptions) that:

  1. 堆栈内存分配在一个连续的块中.
  2. 堆栈向下"增长,从较高地址向较低地址.
  3. 系统在分配的堆栈空间底部附近需要一些空间,以允许优雅地处理堆栈外异常.我们不知道确切的保留空间,但我们会尝试保守地限制它.

有了这些假设,我们可以调用 VirtualQuery获得已分配堆栈的起始地址,然后从某个堆栈分配变量的地址中减去它(使用不安全代码获得).进一步减去我们对系统在堆栈底部所需空间的估计值,我们将得到一个估计值可用空间.

With these assumptions, we could pinvoke VirtualQuery to obtain the start address of the allocated stack, and subtract it from the address of some stack-allocated variable (obtained with unsafe code.) Further subtracting our estimate of the space the system needs at the bottom of the stack would give us an estimate of the available space.

下面的代码通过调用递归函数并写出剩余的估计堆栈空间(以字节为单位)来演示这一点:

The code below demonstrates this by invoking a recursive function and writing out the remaining estimated stack space, in bytes, as it goes:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1 {
    class Program {
        private struct MEMORY_BASIC_INFORMATION {
            public uint BaseAddress;
            public uint AllocationBase;
            public uint AllocationProtect;
            public uint RegionSize;
            public uint State;
            public uint Protect;
            public uint Type;
        }

        private const uint STACK_RESERVED_SPACE = 4096 * 16;

        [DllImport("kernel32.dll")]
        private static extern int VirtualQuery(
            IntPtr                          lpAddress,
            ref MEMORY_BASIC_INFORMATION    lpBuffer,
            int                             dwLength);


        private unsafe static uint EstimatedRemainingStackBytes() {
            MEMORY_BASIC_INFORMATION    stackInfo   = new MEMORY_BASIC_INFORMATION();
            IntPtr                      currentAddr = new IntPtr((uint) &stackInfo - 4096);

            VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));
            return (uint) currentAddr.ToInt64() - stackInfo.AllocationBase - STACK_RESERVED_SPACE;
        }

        static void SampleRecursiveMethod(int remainingIterations) {
            if (remainingIterations <= 0) { return; }

            Console.WriteLine(EstimatedRemainingStackBytes());

            SampleRecursiveMethod(remainingIterations - 1);
        }

        static void Main(string[] args) {
            SampleRecursiveMethod(100);
            Console.ReadLine();
        }
    }
}

这里是前 10 行输出(intel x64、.NET 4.0、调试).给定 1MB 的默认堆栈大小,计数似乎是合理的.

And here are the first 10 lines of output (intel x64, .NET 4.0, debug). Given the 1MB default stack size, the counts appear plausible.

969332
969256
969180
969104
969028
968952
968876
968800
968724
968648

为简洁起见,上面的代码假定页面大小为 4K.虽然这适用于 x86 和 x64,但对于其他受支持的 CLR 架构可能并不正确.您可以调用 GetSystemInfo 来获取机器的页面大小(SYSTEM_INFO 结构).

For brevity, the code above assumes a page size of 4K. While that holds true for x86 and x64, it might not be correct for other supported CLR architectures. You could pinvoke into GetSystemInfo to obtain the machine's page size (the dwPageSize of the SYSTEM_INFO struct).

请注意,这种技术不是特别便携,也不是面向未来的.pinvoke 的使用限制了这种方法在 Windows 主机上的实用性.关于 CLR 堆栈的连续性和增长方向的假设可能适用于当前的 Microsoft 实现.但是,我对 CLI 标准的阅读(可能有限)(通用语言基础结构,PDF,长读)似乎不需要那么多线程堆栈.就 CLI 而言,每个方法调用都需要一个栈帧;但是,如果堆栈向上增长,局部变量堆栈是否与返回值堆栈分开,或者堆栈帧是否在堆上分配,它都不会在意.

Note that this technique isn't particularly portable, nor is it future proof. The use of pinvoke limits the utility of this approach to Windows hosts. The assumptions about the continuity and direction of growth of the CLR stack may hold true for the present Microsoft implementations. However, my (possibly limited) reading of the CLI standard (common language infrastructure, PDF, a long read) does not appear to demand as much of thread stacks. As far as the CLI is concerned, each method invocation requires a stack frame; it couldn't care less, however, if stacks grow upward, if local variable stacks are separate from return value stacks, or if stack frames are allocated on the heap.

这篇关于在 C# 中检查堆栈大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆