StringBuilder char accessor

Looking at the code with a decompiler, I could reconstruct how WinDbg processes the output of the commands:

Whenever the debugging engine outputs something, append it to a buffer (a StringBuilder )

) In the background, run a loop that parses a few nodes from the buffer (DML looks a lot like HTML), refreshes the UI, parses a few more nodes, and so on

If at some point there are no more nodes in the buffer, clear it

The important part to understand is that the parsing loop maintains an index indicating how much of the buffer it had parsed, but clears the buffer only when it is fully processed.

The parsing code itself is straightforward, iterating character by character to detect the beginning and the end of the nodes and creating the corresponding data structures. Individual characters are accessed using the StringBuilder indexer, which appears as StringBuilder.get_Chars in the profiler trace.

But how many times does the DML parser uses the indexer to consume that much CPU? The answer is… less than you would think.

Using the indexer of a string to get an individual char is an O(1) operation. As a string is really just a fancy array of chars, you only need some bound checking before fetching the value. Intuitively, one would expect a similar behavior from a StringBuilder . However, the benchmarks tell a whole different story:

This benchmark measures the time needed to access the first character of StringBuilder instances of different sizes. At a glance, it’s pretty obvious that the access time degrades non-linearly with the number of characters. So what’s going on under the hood?

For reference, here is the same benchmark using a String indexer instead:

StringBuilder is a class that pre-allocates an array of that you can use to concatenate a variable number of strings while limiting the number of allocations. In that sense, it works a lot like List<char> . However, since .NET 4.0, the way it grows when reaching the limit as changed: instead of allocating a bigger array and transferring the contents of the previous array, like List<T> would do, it allocates a new StringBuilder and chains to it. This has a double benefit: when dealing with very large string, it avoids copying large buffers, which would be costly, and keeps the size of individual buffers in check so they don’t end up in the LOH. So in the end, you can think of the StringBuilder as a linked list of char arrays.

It also comes with some drawbacks, as the benchmark puts in evidence. To retrieve a specific character, you need to browse the chain of StringBuilder instances until finding the one containing the character at the right index. It means that getting a character at a given index is actually an O(n) operation, where n is the number of chained instances. And it gets worse: the most commonly used operation with a StringBuilder is Append , which appends a string to the end of the buffer. Keeping that in mind, the StringBuilders are chained from the end: your StringBuilder reference points to the last of the chain, and each of them holds a reference to the previous one. This makes Append operations faster, since you don’t have to browse the whole chain to append your characters. But on the other hand, it makes accessing the beginning of the string much more expensive, since the whole chain has to be browsed. See in this benchmark how much it costs to access the first character versus accessing the last, with a StringBuilder of 10 million characters: