是否可以可靠地跟踪堆栈高度/递归深度?
Is it possible to track the stack height / recursion depth reliably?
我试图在我的程序的某个部分可靠地跟踪我的堆栈高度,以便自适应地调整机器学习算法。
现在,我的代码如下所示:
private const int MaxStackHeight = 20;
[ThreadStatic]
private static int stackHeight;
...
try
{
var currentHeight = Interlocked.Increment(ref stackHeight);
var depthFactor = currentHeight / (double)MaxStackHeight;
// Use `depthFactor` to limit the amount of branching & recursion at this depth by choosing simpler candidates.
}
finally
{
Interlocked.Decrement(ref stackHeight);
}
我知道将 Interlocked
与 ThreadStatic
字段一起使用是不必要的,而且我知道 Constrained Execution Regions。但是,这个问题与 Monitor.Enter
/Monitor.Exit
问题非常相似,所以我不确定使用 CER 可以解决这个问题。例如,Monitor
的解决方案是使用 .NET 4 中添加的 Monitor.TryEnter
重载之一可用的 out lockTaken
参数。Interlocked.Increment
是否有类似的策略?
Monitor (.NET 4+) 的示例解决方案:
var lockTaken = false;
try
{
Monitor.TryEnter(handle, ref lockTaken);
}
finally
{
if (lockTaken)
{
Monitor.Exit(handle);
}
}
之所以可行,是因为 finally 块是一个受约束的执行区域,并且因为 .NET Framework 保证 out lockTaken
参数将被准确设置。
如果这不可能,我可以采用两种选择之一。
- 在堆栈上传递堆栈的高度(例如,将其作为方法参数传递)。这显着增加了我的代码库中许多方法的复杂性。
- 在根调用点,在调用递归部分之前和之后(即使在异常情况下)将
stackHeight
设置为 0
。这感觉……糟透了。
所以,是我的用例可能有类似的东西,还是我需要求助于替代方法?
编辑:
最佳猜测:
var incremented = false;
try
{
RuntimeHelpers.PrepareConstrainedRegions();
try
{
}
finally
{
stackHeight++;
incremented = true;
}
// Use `stackHeight`
}
finally
{
if (incremented)
{
stackHeight--;
}
}
我会在递归递归调用之前保留该值,并在需要时恢复它,例如:
var currentStackHeight = stackHeight;
try {
for(var i = 0; i < 10; i++) {
stackHeight = currentStackHeight + 1;
recurseDeeper();
}
}
finally {
stackHeight = currentStackHeight;
}
我试图在我的程序的某个部分可靠地跟踪我的堆栈高度,以便自适应地调整机器学习算法。
现在,我的代码如下所示:
private const int MaxStackHeight = 20;
[ThreadStatic]
private static int stackHeight;
...
try
{
var currentHeight = Interlocked.Increment(ref stackHeight);
var depthFactor = currentHeight / (double)MaxStackHeight;
// Use `depthFactor` to limit the amount of branching & recursion at this depth by choosing simpler candidates.
}
finally
{
Interlocked.Decrement(ref stackHeight);
}
我知道将 Interlocked
与 ThreadStatic
字段一起使用是不必要的,而且我知道 Constrained Execution Regions。但是,这个问题与 Monitor.Enter
/Monitor.Exit
问题非常相似,所以我不确定使用 CER 可以解决这个问题。例如,Monitor
的解决方案是使用 .NET 4 中添加的 Monitor.TryEnter
重载之一可用的 out lockTaken
参数。Interlocked.Increment
是否有类似的策略?
Monitor (.NET 4+) 的示例解决方案:
var lockTaken = false;
try
{
Monitor.TryEnter(handle, ref lockTaken);
}
finally
{
if (lockTaken)
{
Monitor.Exit(handle);
}
}
之所以可行,是因为 finally 块是一个受约束的执行区域,并且因为 .NET Framework 保证 out lockTaken
参数将被准确设置。
如果这不可能,我可以采用两种选择之一。
- 在堆栈上传递堆栈的高度(例如,将其作为方法参数传递)。这显着增加了我的代码库中许多方法的复杂性。
- 在根调用点,在调用递归部分之前和之后(即使在异常情况下)将
stackHeight
设置为0
。这感觉……糟透了。
所以,是我的用例可能有类似的东西,还是我需要求助于替代方法?
编辑:
最佳猜测:
var incremented = false;
try
{
RuntimeHelpers.PrepareConstrainedRegions();
try
{
}
finally
{
stackHeight++;
incremented = true;
}
// Use `stackHeight`
}
finally
{
if (incremented)
{
stackHeight--;
}
}
我会在递归递归调用之前保留该值,并在需要时恢复它,例如:
var currentStackHeight = stackHeight;
try {
for(var i = 0; i < 10; i++) {
stackHeight = currentStackHeight + 1;
recurseDeeper();
}
}
finally {
stackHeight = currentStackHeight;
}