Skip to content

Instantly share code, notes, and snippets.

@YairHalberstadt
Created December 2, 2020 06:58
Show Gist options
  • Save YairHalberstadt/1df8fbeb846d6de0f4b701af314df26a to your computer and use it in GitHub Desktop.
Save YairHalberstadt/1df8fbeb846d6de0f4b701af314df26a to your computer and use it in GitHub Desktop.
Stringbuilder optimization
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Text
{
/// <summary>
/// Implementation of <see cref="SourceText"/> based on a <see cref="StringBuilder"/> input
/// </summary>
internal sealed partial class StringBuilderText : SourceText
{
/// <summary>
/// Underlying string on which this SourceText instance is based
/// </summary>
private readonly StringBuilder _builder;
private readonly Encoding? _encodingOpt;
#if NETCOREAPP
private int _currentChunkStart;
private StringBuilder.ChunkEnumerator _chunkEnumerator;
private readonly object _chunkLock = new object();
#endif
public StringBuilderText(StringBuilder builder, Encoding? encodingOpt, SourceHashAlgorithm checksumAlgorithm = SourceHashAlgorithm.Sha1)
: base(checksumAlgorithm: checksumAlgorithm)
{
RoslynDebug.Assert(builder != null);
_builder = builder;
_encodingOpt = encodingOpt;
#if NETCOREAPP
_chunkEnumerator = builder.GetChunks();
_chunkEnumerator.MoveNext();
_currentChunkStart = 0;
#endif
}
public override Encoding? Encoding
{
get { return _encodingOpt; }
}
/// <summary>
/// Underlying string which is the source of this SourceText instance
/// </summary>
internal StringBuilder Builder
{
get { return _builder; }
}
/// <summary>
/// The length of the text represented by <see cref="StringBuilderText"/>.
/// </summary>
public override int Length
{
get { return _builder.Length; }
}
/// <summary>
/// Returns a character at given position.
/// </summary>
/// <param name="position">The position to get the character from.</param>
/// <returns>The character.</returns>
/// <exception cref="ArgumentOutOfRangeException">When position is negative or
/// greater than <see cref="Length"/>.</exception>
public override char this[int position]
{
get
{
if (position < 0 || position >= _builder.Length)
{
throw new ArgumentOutOfRangeException(nameof(position));
}
#if NETCOREAPP
// StringBuilder index is O(n),
// so optimize the common case of iterating through the text from begining to end using ChunkEnumerator.
// This optimization still works if theres a bit of backtracking, so long as we never skip over a character completely.
var currentChunkIdx = position - _currentChunkStart;
var currentChunk = _chunkEnumerator.Current.Span;
if (currentChunkIdx <= currentChunk.Length && currentChunkIdx >= 0)
{
lock (_chunkLock)
{
currentChunkIdx = position - _currentChunkStart;
currentChunk = _chunkEnumerator.Current.Span;
if (currentChunkIdx < currentChunk.Length && currentChunkIdx >= 0)
{
return currentChunk[currentChunkIdx];
}
else if (currentChunkIdx == currentChunk.Length)
{
_currentChunkStart += currentChunk.Length;
do
{
_chunkEnumerator.MoveNext();
currentChunk = _chunkEnumerator.Current.Span;
}
while (currentChunk.Length == 0);
return currentChunk[0];
}
}
}
#endif
return _builder[position];
}
}
/// <summary>
/// Provides a string representation of the StringBuilderText located within given span.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">When given span is outside of the text range.</exception>
public override string ToString(TextSpan span)
{
if (span.End > _builder.Length)
{
throw new ArgumentOutOfRangeException(nameof(span));
}
return _builder.ToString(span.Start, span.Length);
}
public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
{
_builder.CopyTo(sourceIndex, destination, destinationIndex, count);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment