I am looking at the Roslyn September 2012 CTP with Reflector, and I noticed the following depth-first traversal of the syntax tree:
private IEnumerable<CommonSyntaxNode> DescendantNodesOnly(TextSpan span,
Func<CommonSyntaxNode, bool> descendIntoChildren, bool includeSelf)
{
if (includeSelf && IsInSpan(span, FullSpan))
{
yield return this;
}
if ((descendIntoChildren != null) && !descendIntoChildren(this))
{
yield break;
}
var queue = new Queue<StrongBox<IEnumerator<CommonSyntaxNode>>>();
var stack = new Stack<StrongBox<IEnumerator<CommonSyntaxNode>>>();
stack.Push(new StrongBox<IEnumerator<CommonSyntaxNode>>(ChildNodes().GetEnumerator()));
while (stack.Count > 0)
{
var enumerator = stack.Peek();
StrongBox<IEnumerator<CommonSyntaxNode>> childEnumerator;
if (enumerator.Value.MoveNext())
{
var current = enumerator.Value.Current;
if (IsInSpan(span, current.FullSpan))
{
yield return current;
if ((descendIntoChildren == null) || descendIntoChildren(current))
{
childEnumerator = queue.Count == 0
? new StrongBox<IEnumerator<CommonSyntaxNode>>()
: queue.Dequeue();
childEnumerator.Value = current.ChildNodes().GetEnumerator();
stack.Push(childEnumerator);
}
}
}
else
{
childEnumerator = stack.Pop();
childEnumerator.Value = null;
queue.Enqueue(childEnumerator);
}
}
}
I am guessing that the queue is to ease the runtime from allocating and deallocating so many instances of IEnumerator<CommonSyntaxNode>
.
However, I am not sure why IEnumerator<CommonSyntaxNode>
is wrapped in StrongBox<>
. What sort of performance and safety trade-offs are involved in wrapping IEnumerator<CommonSyntaxNode>
, which is usually a value type, inside the reference type StrongBox<>
?
CommonSyntaxNode
is an abstact class which contains alot of value types and can be inherited into a big object.
IEnumerator<CommonSyntaxNode>
contains only a reference to a CommonSyntaxNode
so it seems like the size of the CommonSyntaxNode
won't affect the Enumerator
size since it's only a reference, but:
since IEnumerator<T>
's method MoveNext()
uses yield return;
each iteration in the Enumerator
will cause the method to save it's state untill the next iteration.
since the whole method state is heavy enough and it might contain CommonSyntaxNode
's properties in order to do the MoveNext()
logic, than the whole IEnumerator<CommonSyntaxNode>
might be pretty heavy on memory.
using StrongBox<>
causes the Queue
or Stack
hold only a small reference object (the StrongBox<>
instead of the potentially heavy on memory IEnumerator<CommonSyntaxNode>
- therefore - the GC is cleaning the Queue
or Stack
's contained IEnumerator<CommonSyntaxNode>
from the memory potentially faster - reducing the application total memory consumption.
note that CommonSyntaxNode
's enumerator is a struct which working with it directly means deeply copying the whole struct, it's a small struct so it's not really heavy but still...
The advantage of the StrongBox<T>
is once an item is dequeued, the StrongBox clears out it's internal content - so the GC can then collect the instance of T being held by StrongBox and the Queue<T>
ends up holding just an instance of StrongBox (instead of an instance of T).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With