Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
793 views
in Technique[技术] by (71.8m points)

multithreading - Checking stack size in C#

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.

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. Stack memory is allocated in a contiguous block.
  2. The stack grows 'downwards', from higher addresses towards lower addresses.
  3. The system needs some space near the bottom of the allocated stack space to allow graceful handling of out-of-stack exceptions. We don't know the exact reserved space, but we'll attempt to conservatively bound it.

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();
        }
    }
}

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

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).

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...