| File: FrameworkFork\Microsoft.Xml\Xml\BufferBuilder.cs | Web Access |
| Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib) |
// 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. //#define BUFFER_BUILDER_TRACING using System.IO; using System.Text; using System.Diagnostics; namespace Microsoft.Xml { using System; // // Buffer Builder // // BufferBuilder is a replacement for StringBuilder for cases when large strings can occur. // StringBuilder stores the string that is being built in one large chunk of memory. If it needs more memory, // it allocates a new chunk of double size and copies the data into it. This results in bad perf and // memory constumption in case the string is very large (>85kB). Large objects are allocated on Large Object // Heap and are not freed by GC as fast as smaller objects. // // BufferBuilder uses a StringBuilder as long as the stored string is smaller that 64kB. If the final string // should be bigger that that, it stores the data in a list of char[] arrays. A StringBuilder object still needs to be // used in order to create the final string in ToString methods, but this is ok since at that point // we already know the resulting string length and we can initialize the StringBuilder with the correct // capacity. // // The BufferBuilder is designed for reusing. The Clear method will clear the state of the builder. // The next string built by BufferBuilder will reuse the string builder and the buffer chunks allocated // in the previous uses. (The string builder it not reused when it was last used to create a string >64kB because // setting Length=0 on the string builder makes it allocate the big string again.) // When the buffer chunks are not in use, they are stored as WeakReferences so they can be freed by GC // in case memory-pressure situation happens. #if BUFFER_BUILDER_TRACING public class BufferBuilder { #else internal class BufferBuilder { #endif // // Private types // private struct Buffer { internal char[] buffer; internal WeakReference recycledBuffer; } // // Fields // private StringBuilder _stringBuilder; private Buffer[] _buffers; private int _buffersCount; private char[] _lastBuffer; private int _lastBufferIndex; private int _length; #if BUFFER_BUILDER_TRACING // // Tracing // public static TextWriter s_TraceOutput = null; static int minLength = int.MaxValue; static int maxLength; static int totalLength; static int toStringCount; static int totalAppendCount; #endif // // Constants // #if DEBUG // make it easier to catch buffer-related bugs on debug builds private const int BufferSize = 4 * 1024; #else private const int BufferSize = 64 * 1024; #endif private const int InitialBufferArrayLength = 4; private const int MaxStringBuilderLength = BufferSize; private const int DefaultSBCapacity = 16; // // Constructor // public BufferBuilder() { #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "----------------------------\r\nnew BufferBuilder()\r\n----------------------------" ); } #endif } // // Properties // public int Length { get { return _length; } set { #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "BufferBuilder.Length = " + value ); } #endif if (value < 0 || value > _length) { throw new ArgumentOutOfRangeException("value"); } if (value == 0) { Clear(); } else { SetLength(value); } } } // // Public methods // public void Append(char value) { #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "BufferBuilder.Append\tLength = 1\tchar '" + value.ToString() + "'" ); totalAppendCount++; } #endif if (_length + 1 <= MaxStringBuilderLength) { if (_stringBuilder == null) { _stringBuilder = new StringBuilder(); } _stringBuilder.Append(value); } else { if (_lastBuffer == null) { CreateBuffers(); } if (_lastBufferIndex == _lastBuffer.Length) { AddBuffer(); } _lastBuffer[_lastBufferIndex++] = value; } _length++; } public void Append(char[] value, int start, int count) { #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "BufferBuilder.Append\tLength = " + count + "\t char array \"" + new string( value, start, count ) + "\"" ); totalAppendCount++; } #endif if (value == null) { if (start == 0 && count == 0) { return; } throw new ArgumentNullException("value"); } if (count == 0) { return; } if (start < 0) { throw new ArgumentOutOfRangeException("start"); } if (count < 0 || start + count > value.Length) { throw new ArgumentOutOfRangeException("count"); } if (_length + count <= MaxStringBuilderLength) { if (_stringBuilder == null) { _stringBuilder = new StringBuilder(count < DefaultSBCapacity ? DefaultSBCapacity : count); } _stringBuilder.Append(value, start, count); _length += count; } else { unsafe { fixed (char* source = &value[start]) { AppendHelper(source, count); } } } } public void Append(string value) { Append(value, 0, value.Length); } public void Append(string value, int start, int count) { #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "BufferBuilder.Append\tLength = " + count + "\t string fragment \"" + value.Substring( start, count ) + "\"" ); totalAppendCount++; } #endif if (value == null) { if (start == 0 && count == 0) { return; } throw new ArgumentNullException("value"); } if (count == 0) { return; } if (start < 0) { throw new ArgumentOutOfRangeException("start"); } if (count < 0 || start + count > value.Length) { throw new ArgumentOutOfRangeException("count"); } if (_length + count <= MaxStringBuilderLength) { if (_stringBuilder == null) { _stringBuilder = new StringBuilder(value, start, count, 0); } else { _stringBuilder.Append(value, start, count); } _length += count; } else { unsafe { fixed (char* source = value) { AppendHelper(source + start, count); } } } } public void Clear() { if (_length <= MaxStringBuilderLength) { if (_stringBuilder != null) { _stringBuilder.Length = 0; } } else { if (_lastBuffer != null) { ClearBuffers(); } // destroy the string builder because setting its Length or Capacity to 0 makes it allocate the last string again :-| _stringBuilder = null; } _length = 0; } internal void ClearBuffers() { if (_buffers != null) { // recycle all but the first the buffer for (int i = 0; i < _buffersCount; i++) { Recycle(ref _buffers[i]); } _lastBuffer = null; } else { // just one buffer allocated with no buffers array -> no recycling } _lastBufferIndex = 0; _buffersCount = 0; } public override string ToString() { string returnString; if ((_length <= MaxStringBuilderLength) || (_buffersCount == 1 && _lastBufferIndex == 0)) { returnString = (_stringBuilder != null) ? _stringBuilder.ToString() : string.Empty; } else { if (_stringBuilder == null) { _stringBuilder = new StringBuilder(_length); } else { _stringBuilder.Capacity = _length; } int charsLeft = _length - _stringBuilder.Length; for (int i = 0; i < _buffersCount - 1; i++) { char[] buf = _buffers[i].buffer; _stringBuilder.Append(buf, 0, buf.Length); charsLeft -= buf.Length; } _stringBuilder.Append(_buffers[_buffersCount - 1].buffer, 0, charsLeft); ClearBuffers(); returnString = _stringBuilder.ToString(); } #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "BufferBuilder.ToString() Length == " + returnString.Length + "\t \"" + returnString + "\"" ); toStringCount++; totalLength += returnString.Length; if ( minLength > returnString.Length ) { minLength = returnString.Length; } if ( maxLength < returnString.Length ) { maxLength = returnString.Length; } } #endif return returnString; } public string ToString(int startIndex, int len) { #if BUFFER_BUILDER_TRACING if ( s_TraceOutput != null ) { s_TraceOutput.WriteLine( "BufferBuilder.ToString( " + startIndex + ", " + len + " )" ); } #endif if (startIndex < 0 || startIndex >= _length) { throw new ArgumentOutOfRangeException("startIndex"); } if (len < 0 || startIndex + len > _length) { throw new ArgumentOutOfRangeException("len"); } if ((_length <= MaxStringBuilderLength) || (_buffersCount == 1 && _lastBufferIndex == 0)) { return (_stringBuilder != null) ? _stringBuilder.ToString(startIndex, len) : string.Empty; } else { StringBuilder sb = new StringBuilder(len); if (_stringBuilder != null) { if (startIndex < _stringBuilder.Length) { if (len < _stringBuilder.Length) { return _stringBuilder.ToString(startIndex, len); } else { sb.Append(_stringBuilder.ToString(startIndex, _stringBuilder.Length - startIndex)); len -= (_stringBuilder.Length - startIndex); startIndex = 0; } } else { startIndex -= _stringBuilder.Length; } } int i; for (i = 0; i < _buffersCount; i++) { if (startIndex < _buffers[i].buffer.Length) { break; } startIndex -= _buffers[i].buffer.Length; } if (i < _buffersCount) { int charsLeft = len; for (; i < _buffersCount && charsLeft > 0; i++) { char[] buf = _buffers[i].buffer; int copyCount = (buf.Length < charsLeft) ? buf.Length : charsLeft; sb.Append(buf, startIndex, copyCount); startIndex = 0; charsLeft -= copyCount; } } return sb.ToString(); } } // // Private implementation methods // private void CreateBuffers() { Debug.Assert(_lastBuffer == null); if (_buffers == null) { _lastBuffer = new char[BufferSize]; _buffers = new Buffer[InitialBufferArrayLength]; _buffers[0].buffer = _lastBuffer; _buffersCount = 1; } else { AddBuffer(); } } unsafe private void AppendHelper(char* pSource, int count) { if (_lastBuffer == null) { CreateBuffers(); } int copyCount = 0; while (count > 0) { if (_lastBufferIndex >= _lastBuffer.Length) { AddBuffer(); } copyCount = count; int free = _lastBuffer.Length - _lastBufferIndex; if (free < copyCount) { copyCount = free; } fixed (char* pLastBuffer = &_lastBuffer[_lastBufferIndex]) { wstrcpy(pLastBuffer, pSource, copyCount); } pSource += copyCount; _length += copyCount; _lastBufferIndex += copyCount; count -= copyCount; } } private void AddBuffer() { Debug.Assert(_buffers != null); // check the buffers array it its big enough if (_buffersCount + 1 == _buffers.Length) { Buffer[] newBuffers = new Buffer[_buffers.Length * 2]; Array.Copy(_buffers, 0, newBuffers, 0, _buffers.Length); _buffers = newBuffers; } // use the recycled buffer if we have one char[] newBuffer; if (_buffers[_buffersCount].recycledBuffer != null) { newBuffer = (char[])_buffers[_buffersCount].recycledBuffer.Target; if (newBuffer != null) { _buffers[_buffersCount].recycledBuffer.Target = null; goto End; } } newBuffer = new char[BufferSize]; End: // add the buffer to the list _lastBuffer = newBuffer; _buffers[_buffersCount++].buffer = newBuffer; _lastBufferIndex = 0; } private void Recycle(ref Buffer buf) { // recycled buffers are kept as WeakReferences if (buf.recycledBuffer == null) { buf.recycledBuffer = new WeakReference(buf.buffer); } else { buf.recycledBuffer.Target = buf.buffer; } #if DEBUG for (int i = 0; i < buf.buffer.Length; i++) { buf.buffer[i] = (char)0xCC; } #endif buf.buffer = null; } private void SetLength(int newLength) { Debug.Assert(newLength <= _length); if (newLength == _length) { return; } if (_length <= MaxStringBuilderLength) { _stringBuilder.Length = newLength; } else { int newLastIndex = newLength; int i; for (i = 0; i < _buffersCount; i++) { if (newLastIndex < _buffers[i].buffer.Length) { break; } newLastIndex -= _buffers[i].buffer.Length; } if (i < _buffersCount) { _lastBuffer = _buffers[i].buffer; _lastBufferIndex = newLastIndex; i++; int newBuffersCount = i; for (; i < _buffersCount; i++) { Recycle(ref _buffers[i]); } _buffersCount = newBuffersCount; } } _length = newLength; } internal static unsafe void wstrcpy(char* dmem, char* smem, int charCount) { if (charCount > 0) { if ((((int)dmem ^ (int)smem) & 3) == 0) { while (((int)dmem & 3) != 0 && charCount > 0) { dmem[0] = smem[0]; dmem += 1; smem += 1; charCount -= 1; } if (charCount >= 8) { charCount -= 8; do { ((uint*)dmem)[0] = ((uint*)smem)[0]; ((uint*)dmem)[1] = ((uint*)smem)[1]; ((uint*)dmem)[2] = ((uint*)smem)[2]; ((uint*)dmem)[3] = ((uint*)smem)[3]; dmem += 8; smem += 8; charCount -= 8; } while (charCount >= 0); } if ((charCount & 4) != 0) { ((uint*)dmem)[0] = ((uint*)smem)[0]; ((uint*)dmem)[1] = ((uint*)smem)[1]; dmem += 4; smem += 4; } if ((charCount & 2) != 0) { ((uint*)dmem)[0] = ((uint*)smem)[0]; dmem += 2; smem += 2; } } else { if (charCount >= 8) { charCount -= 8; do { dmem[0] = smem[0]; dmem[1] = smem[1]; dmem[2] = smem[2]; dmem[3] = smem[3]; dmem[4] = smem[4]; dmem[5] = smem[5]; dmem[6] = smem[6]; dmem[7] = smem[7]; dmem += 8; smem += 8; charCount -= 8; } while (charCount >= 0); } if ((charCount & 4) != 0) { dmem[0] = smem[0]; dmem[1] = smem[1]; dmem[2] = smem[2]; dmem[3] = smem[3]; dmem += 4; smem += 4; } if ((charCount & 2) != 0) { dmem[0] = smem[0]; dmem[1] = smem[1]; dmem += 2; smem += 2; } } if ((charCount & 1) != 0) { dmem[0] = smem[0]; } } } #if BUFFER_BUILDER_TRACING public static int ToStringCount { get { return toStringCount; } } public static double AvgAppendCount { get { return toStringCount == 0 ? 0 : (double)totalAppendCount / toStringCount; } } public static int AvgLength { get { return toStringCount == 0 ? 0 : totalLength / toStringCount; } } public static int MaxLength { get { return maxLength; } } public static int MinLength { get { return minLength; } } #endif } }