2010-05-24 43 views
10

¿Hay alguna forma de comprobar el tamaño de la pila de subprocesos en C#?Comprobación del tamaño de la pila en C#

+3

¿Por qué quieres? –

+0

Hasta donde yo sé, no puedes. Al menos no usando un método nativo. – Propeng

+0

Me gustaría saber qué cantidad de la pila se utiliza en un determinado momento. Digamos que invoco un método recursivo 10 veces, quiero saber qué cantidad de apilamiento se usa (o izquierda) en ese punto – Gjorgji

Respuesta

14

Este es un caso de if you have to ask, you can't afford it (Raymond Chen dijo primero.) Si el código depende de que haya suficiente espacio de pila en la medida en que tiene que comprobar primero, podría valer la pena refactorearlo utilizar un explícito Stack<T> objeto en su lugar. En su lugar, hay un mérito en el comentario de John sobre el uso de un generador de perfiles.

Dicho esto, resulta que hay una manera de estimar el espacio restante de la pila. No es preciso, pero es lo suficientemente útil para evaluar qué tan cerca está de la parte inferior. Lo siguiente se basa en gran medida en excellent article by Joe Duffy.

Sabemos (o haremos los supuestos) que:

  1. la memoria de pila se asigna en un bloque contiguo.
  2. La pila crece 'hacia abajo', desde direcciones más altas hacia direcciones inferiores.
  3. El sistema necesita algo de espacio cerca de la parte inferior del espacio de pila asignado para permitir el manejo ordenado de las excepciones fuera de la pila. No conocemos el espacio reservado exacto, pero intentaremos enlazarlo de manera conservadora.

Con estos supuestos, podríamos PInvoke VirtualQuery para obtener la dirección de inicio de la pila asignado, y restarlo de la dirección de una variable de pila asignado (obtenido con código no seguro.) Restando aún más nuestra estimación del espacio el sistema necesita en la parte inferior de la pila nos daría una estimación del espacio disponible.

El código siguiente muestra este al invocar una función recursiva y la escritura a cabo el espacio de pila restante estimado, en bytes, ya que pasa:

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

Y aquí están las primeras 10 líneas de salida (x64 de Intel, .NET 4.0, depuración). Dado el tamaño de pila predeterminado de 1MB, los recuentos parecen plausibles.

969332 
969256 
969180 
969104 
969028 
968952 
968876 
968800 
968724 
968648 

Por brevedad, el código de arriba supone un tamaño de página de 4K. Si bien eso es cierto para x86 y x64, podría no ser correcto para otras arquitecturas CLR soportadas. Puede pinvoke en GetSystemInfo para obtener el tamaño de página de la máquina (el dwPageSize de la estructura SYSTEM_INFO).

Tenga en cuenta que esta técnica no es particularmente portátil, ni es una prueba de futuro. El uso de pinvoke limita la utilidad de este enfoque para los hosts de Windows. Las suposiciones sobre la continuidad y la dirección del crecimiento de la pila CLR pueden ser válidas para las implementaciones actuales de Microsoft. Sin embargo, mi lectura (posiblemente limitada) de CLI standard (infraestructura de lenguaje común, PDF, una lectura larga) no parece exigir tanto de las pilas de subprocesos. En lo que respecta a la CLI, cada invocación de método requiere un marco de pila; Sin embargo, no podría importarle menos si las pilas crecen hacia arriba, si las pilas de variables locales están separadas de las pilas de valores de retorno, o si los marcos de pila se asignan en el montón.

+0

+1 para explicación y detalle. –

+1

Si uno pidiera un número constante, "cuánta pila puede usar un programa de forma segura", estaría de acuerdo con la filosofía "IYHTA, YCAI". Por otro lado, si uno escribe algo así como un analizador sintáctico donde podría recurrirse para manejar cualquier nivel esperado de estructuras anidadas en la entrada, parecería más limpio tener el cheque recursivo restante en el espacio de pila y lanzar un "anidamiento demasiado profundo" "excepción si fue inadecuada, que imponer alguna limitación arbitraria en la anidación". – supercat

+1

Esta comprobación también puede ser útil en la depuración para establecer un punto de interrupción en la misma situación en la que se está ejecutando hacia un desbordamiento de pila. Un punto de interrupción le permitirá ir al principio de la pila de llamadas e inspeccionar todas las variables. Tan pronto como se lanza la StackOverflowException, Visual Studio ya no puede leer variables, ya es demasiado tarde. – ygoe

Cuestiones relacionadas