From 467bff343cfabbdad9024c45c105fa8470e5b3fa Mon Sep 17 00:00:00 2001 From: yesmey Date: Wed, 14 Sep 2022 12:18:25 +0200 Subject: [PATCH] Refactor StreamReader ReadLine to use Span IndexOfAny --- .../src/System/IO/StreamReader.cs | 164 +++++++++--------- 1 file changed, 80 insertions(+), 84 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs index b0d39ede93eead..7023cfd6b2cddc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @@ -780,53 +780,23 @@ private int ReadBuffer(Span userBuffer, out bool readToUserBuffer) ThrowIfDisposed(); CheckAsyncTaskInProgress(); - if (_charPos == _charLen) - { - if (ReadBuffer() == 0) - { - return null; - } - } - StringBuilder? sb = null; - do + bool spilledLineFeed = false; + while (true) { - int i = _charPos; - do + if (_charPos == _charLen) { - char ch = _charBuffer[i]; - // Note the following common line feed chars: - // \n - UNIX \r\n - DOS \r - Mac - if (ch == '\r' || ch == '\n') + if (ReadBuffer() == 0) { - string s; - if (sb != null) - { - sb.Append(_charBuffer, _charPos, i - _charPos); - s = sb.ToString(); - } - else - { - s = new string(_charBuffer, _charPos, i - _charPos); - } - _charPos = i + 1; - if (ch == '\r' && (_charPos < _charLen || ReadBuffer() > 0)) - { - if (_charBuffer[_charPos] == '\n') - { - _charPos++; - } - } - return s; + return sb?.ToString(); } - i++; - } while (i < _charLen); + } - i = _charLen - _charPos; - sb ??= new StringBuilder(i + 80); - sb.Append(_charBuffer, _charPos, i); - } while (ReadBuffer() > 0); - return sb.ToString(); + if (TryReadLine(ref sb, ref spilledLineFeed, out string? result)) + { + return result; + } + } } public override Task ReadLineAsync() => @@ -881,63 +851,89 @@ private int ReadBuffer(Span userBuffer, out bool readToUserBuffer) private async Task ReadLineAsyncInternal(CancellationToken cancellationToken) { - if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0) - { - return null; - } - StringBuilder? sb = null; - - do + bool spilledLineFeed = false; + while (true) { - char[] tmpCharBuffer = _charBuffer; - int tmpCharLen = _charLen; - int tmpCharPos = _charPos; - int i = tmpCharPos; + if (_charPos == _charLen) + { + if ((await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0) + { + return sb?.ToString(); + } + } - do + if (TryReadLine(ref sb, ref spilledLineFeed, out string? result)) { - char ch = tmpCharBuffer[i]; + return result; + } + } + } - // Note the following common line feed chars: - // \n - UNIX \r\n - DOS \r - Mac - if (ch == '\r' || ch == '\n') - { - string s; + private bool TryReadLine(ref StringBuilder? sb, ref bool spilledLineFeed, [NotNullWhen(true)] out string? result) + { + // Special case if the buffer runs out of space between \r and (potentially) \n + if (spilledLineFeed) + { + if (_charBuffer[_charPos] == '\n') + { + _charPos++; + } - if (sb != null) - { - sb.Append(tmpCharBuffer, tmpCharPos, i - tmpCharPos); - s = sb.ToString(); - } - else - { - s = new string(tmpCharBuffer, tmpCharPos, i - tmpCharPos); - } + // sb cannot be null here + result = sb!.ToString(); + return true; + } - _charPos = tmpCharPos = i + 1; + ReadOnlySpan segment = _charBuffer.AsSpan(_charPos, _charLen - _charPos); - if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) > 0)) + int newLineIndex = segment.IndexOfAny('\r', '\n'); + if ((uint)newLineIndex < (uint)segment.Length) + { + _charPos += newLineIndex + 1; + if (segment[newLineIndex] == '\r') + { + int nextCharIndex = newLineIndex + 1; + if ((uint)nextCharIndex < (uint)segment.Length) + { + if (segment[nextCharIndex] == '\n') { - tmpCharPos = _charPos; - if (_charBuffer[tmpCharPos] == '\n') - { - _charPos = ++tmpCharPos; - } + _charPos++; } - - return s; } + else + { + Debug.Assert(_charPos == _charLen); + // At the end of buffer, but there might be another line feed. + // Stash the current string and consume the line feed next iteration + sb ??= new StringBuilder(newLineIndex); + sb.Append(segment.Slice(0, newLineIndex)); + spilledLineFeed = true; + + result = null; + return false; + } + } - i++; - } while (i < tmpCharLen); + segment = segment.Slice(0, newLineIndex); + if (sb != null) + { + sb.Append(segment); + result = sb.ToString(); + } + else + { + result = new string(segment); + } + return true; + } - i = tmpCharLen - tmpCharPos; - sb ??= new StringBuilder(i + 80); - sb.Append(tmpCharBuffer, tmpCharPos, i); - } while (await ReadBufferAsync(cancellationToken).ConfigureAwait(false) > 0); + sb ??= new StringBuilder(segment.Length + 80); + sb.Append(segment); + _charPos = _charLen; - return sb.ToString(); + result = null; + return false; } public override Task ReadToEndAsync() => ReadToEndAsync(default);