Есть ли способ проверить размер стека потоков в С#?
Проверка размера стека в С#
Ответ 1
Это случай если вам нужно спросить, вы не можете себе это позволить (сначала сказал Рэймонд Чен). Если код зависит от наличия достаточного пространства стека в той мере, в какой оно должно быть проверено во-первых, может быть целесообразно реорганизовать его для использования явного Stack<T>
вместо этого. В комментарии Джона есть замечание об использовании профайлера.
Тем не менее, оказывается, что есть способ оценить оставшееся пространство стека. Это неточно, но это достаточно полезно для оценки того, насколько близко вы находитесь. Следующее в значительной степени основано на отличной статье Джо Даффи.
Мы знаем (или сделаем предположения), что:
- Память стека выделяется в непрерывном блоке.
- Стек растет "вниз", от более высоких адресов к более низким адресам.
- Системе требуется некоторое пространство в нижней части выделенного пространства стека, чтобы обеспечить изящную обработку исключений вне стека. Мы не знаем точное зарезервированное пространство, но мы попытаемся его консервативно связать.
С этими предположениями мы могли бы вывести VirtualQuery, чтобы получить начальный адрес выделенного стека и вычесть его из адреса некоторых (полученная с помощью небезопасного кода). Далее, вычитая нашу оценку пространства, которое система нуждается в нижней части стека, дала бы нам оценку доступного пространства.
Приведенный ниже код демонстрирует это, вызывая рекурсивную функцию и записывая оставшееся оцениваемое пространство стека, в байтах, как это происходит:
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, debug). Учитывая размер стека по умолчанию 1 МБ, подсчеты выглядят правдоподобными.
969332
969256
969180
969104
969028
968952
968876
968800
968724
968648
Для краткости вышеприведенный код предполагает размер страницы 4K. Хотя это справедливо для x86 и x64, это может быть некорректно для других поддерживаемых CLR-архитектур. Вы можете прикрепить GetSystemInfo, чтобы получить размер страницы машины (dwPageSize SYSTEM_INFO).
Обратите внимание, что этот метод не является особенно переносимым, и он не является будущим доказательством. Использование pinvoke ограничивает использование этого подхода для хостов Windows. Предположения о непрерывности и направлении роста стека CLR могут иметь место для настоящих реализаций Microsoft. Тем не менее, мое (возможно ограниченное) чтение стандарта