2010-05-24 12 views
10

Gibt es eine Möglichkeit, die Thread-Stapelgröße in C# zu überprüfen?Überprüfen der Stapelgröße in C#

+3

Warum möchten Sie? –

+0

Soweit ich weiß, können Sie nicht. Zumindest nicht mit einer nativen Methode. – Propeng

+0

Ich würde gerne wissen, wie viel von dem Stapel zu einem bestimmten Zeitpunkt verwendet wird. Sagen wir, ich rufe eine rekursive Methode 10 Mal, ich möchte wissen, wie viel der gestapelten verwendet wird (oder links) an diesem Punkt – Gjorgji

Antwort

14

Dies ist ein Fall von if you have to ask, you can't afford it (Raymond Chen es die erste.) Wenn der Code dort ist genügend Stapelspeicherplatz in dem Maße abhängt, dass es zunächst prüfen hat, könnte es sich lohnen, es Refactoring eine explizite Stack<T> zu verwenden Objekt stattdessen. In Johns Kommentar über die Verwendung eines Profilers ist ein Verdienst.

Das heißt, es stellt sich heraus, dass es eine Möglichkeit gibt, den verbleibenden Stapelplatz zu schätzen. Es ist nicht präzise, ​​aber es ist nützlich genug, um zu bewerten, wie nahe am Boden Sie sind. Das Folgende basiert stark auf einem excellent article by Joe Duffy.

Wir wissen (oder werden die Annahmen getroffen werden), dass:

  1. Stapelspeicher wird in einem zusammenhängenden Block zugeordnet.
  2. Der Stack wächst "nach unten", von höheren Adressen zu niedrigeren Adressen.
  3. Das System benötigt etwas Speicherplatz in der Nähe des unteren Rands des zugewiesenen Stapelspeichers, um eine fehlerfreie Behandlung von Out-of-Stack-Ausnahmen zu ermöglichen. Wir kennen den genau reservierten Platz nicht, aber wir werden versuchen, ihn konservativ zu begrenzen.

Mit diesen Annahmen konnten wir VirtualQuery pinvoke die Startadresse des zugeordneten Stapel zu erhalten, und subtrahieren sie von der Adresse einiger Stapel zugewiesene Variable (mit unsicheren Code erhalten.) Weitere unsere Schätzung des Raumes Subtrahieren Die Systemanforderungen unten im Stapel würden uns eine Schätzung des verfügbaren Platzes geben.

Der folgende Code zeigt dies durch eine rekursive Funktion aufgerufen wird und die verbleibende geschätzte Stapelspeicher auszuschreiben, in Bytes, wie es geht:

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

Und hier sind die ersten 10 Zeilen der Ausgabe (Intel x64, .NET 4.0, debuggen). Angesichts der 1 MB Standard-Stack-Größe erscheinen die Zählungen plausibel.

969332 
969256 
969180 
969104 
969028 
968952 
968876 
968800 
968724 
968648 

Der Kürze halber nimmt der obige Code eine Seitengröße von 4K an. Während dies für x86 und x64 gilt, ist es möglicherweise für andere unterstützte CLR-Architekturen nicht korrekt. Sie können in GetSystemInfo pinvoke, um die Seitengröße der Maschine (die dwPageSize der Struktur SYSTEM_INFO) zu erhalten.

Beachten Sie, dass diese Technik nicht besonders portabel ist und auch nicht zukunftssicher ist. Die Verwendung von pinvoke begrenzt den Nutzen dieses Ansatzes für Windows-Hosts. Die Annahmen über die Kontinuität und Richtung des Wachstums des CLR-Stacks können für die vorliegenden Microsoft-Implementierungen gelten. Jedoch scheint meine (möglicherweise begrenzte) Lektüre der CLI standard (gemeinsame Sprachinfrastruktur, PDF, ein langer Lesevorgang) nicht so viel Thread-Stacks zu verlangen. Soweit es die CLI betrifft, erfordert jeder Methodenaufruf einen Stapelrahmen; Es ist jedoch nicht weniger wichtig, wenn Stapel nach oben wachsen, wenn lokale Variablenstapel von Rückgabewertstapeln getrennt sind oder wenn Stapelrahmen auf dem Heap zugeordnet sind.

+0

+1 für Erklärung und Detail. –

+1

Wenn man nach einer konstanten Zahl fragt, "wie viel Stack ein Programm sicher verwenden kann", würde ich der Philosophie "IYHTA, YCAI" zustimmen. Auf der anderen Seite, wenn man etwas wie einen Parser schreibt, wo man Rekursion verwenden könnte, um jede erwartete Ebene verschachtelter Strukturen auf der Eingabe zu handhaben, scheint es sauberer zu sein, die rekursive Überprüfung des verbleibenden Stapelspeichers zu haben und eine "Verschachtelung zu tief" zu werfen "Ausnahme, wenn es nicht angemessen war, als irgendeine willkürliche Beschränkung der Verschachtelung aufzuerlegen. – supercat

+1

Diese Überprüfung kann auch beim Debuggen hilfreich sein, um einen Haltepunkt genau in der Situation festzulegen, in der Sie auf einen Stapelüberlauf hinarbeiten. Ein Unterbrechungspunkt ermöglicht es Ihnen, zum Anfang des Aufruf-Stacks zu gehen und jede Variable zu überprüfen. Sobald die StackOverflowException ausgelöst wurde, kann Visual Studio keine Variablen mehr lesen, es ist zu spät. – ygoe

Verwandte Themen