Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S.IO.StringReader: Use ReadOnlySpan.IndexOfAny in ReadLine() for performance #60463

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static void ReadLine()

using (StringReader sr = new StringReader(str1))
{
Assert.Equal(str1, sr.ReadLine());
Assert.Same(str1, sr.ReadLine());
}
using (StringReader sr = new StringReader(str2))
{
Expand Down
91 changes: 52 additions & 39 deletions src/libraries/System.Private.CoreLib/src/System/IO/StringReader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -11,7 +12,6 @@ public class StringReader : TextReader
{
private string? _s;
private int _pos;
private int _length;

public StringReader(string s)
{
Expand All @@ -21,7 +21,6 @@ public StringReader(string s)
}

_s = s;
_length = s.Length;
}

public override void Close()
Expand All @@ -33,7 +32,6 @@ protected override void Dispose(bool disposing)
{
_s = null;
_pos = 0;
_length = 0;
base.Dispose(disposing);
}

Expand All @@ -44,33 +42,40 @@ protected override void Dispose(bool disposing)
//
public override int Peek()
{
if (_s == null)
string? s = _s;
if (s == null)
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
ThrowObjectDisposedException_ReaderClosed();
}
if (_pos == _length)

int pos = _pos;
if ((uint)pos < s.Length)
nietras marked this conversation as resolved.
Show resolved Hide resolved
{
return -1;
return s[pos];
}

return _s[_pos];
return -1;
}

// Reads the next character from the underlying string. The returned value
// is -1 if no further characters are available.
//
public override int Read()
{
if (_s == null)
string? s = _s;
if (s == null)
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
ThrowObjectDisposedException_ReaderClosed();
}
if (_pos == _length)

int pos = _pos;
if ((uint)pos < s.Length)
nietras marked this conversation as resolved.
Show resolved Hide resolved
{
return -1;
_pos++;
return s[pos];
}

return _s[_pos++];
return -1;
}

// Reads a block of characters. This method will read up to count
Expand Down Expand Up @@ -98,10 +103,10 @@ public override int Read(char[] buffer, int index, int count)
}
if (_s == null)
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
ThrowObjectDisposedException_ReaderClosed();
}

int n = _length - _pos;
int n = _s.Length - _pos;
if (n > 0)
{
if (n > count)
Expand All @@ -126,10 +131,10 @@ public override int Read(Span<char> buffer)

if (_s == null)
nietras marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
ThrowObjectDisposedException_ReaderClosed();
}

int n = _length - _pos;
int n = _s.Length - _pos;
if (n > 0)
{
if (n > buffer.Length)
Expand All @@ -150,7 +155,7 @@ public override string ReadToEnd()
{
if (_s == null)
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
ThrowObjectDisposedException_ReaderClosed();
}

string s;
Expand All @@ -160,10 +165,10 @@ public override string ReadToEnd()
}
else
{
s = _s.Substring(_pos, _length - _pos);
s = _s.Substring(_pos);
}

_pos = _length;
_pos = _s.Length;
return s;
nietras marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -175,38 +180,40 @@ public override string ReadToEnd()
//
public override string? ReadLine()
{
if (_s == null)
string? s = _s;
if (s == null)
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
ThrowObjectDisposedException_ReaderClosed();
}
var pos = _pos;
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
if ((uint)pos >= (uint)s.Length)
return null;
nietras marked this conversation as resolved.
Show resolved Hide resolved

int i = _pos;
while (i < _length)
ReadOnlySpan<char> remaining = s.AsSpan(pos);
int foundLineLength = remaining.IndexOfAny('\r', '\n');
if (foundLineLength >= 0)
{
char ch = _s[i];
if (ch == '\r' || ch == '\n')
string result = s.Substring(pos, foundLineLength);

char ch = remaining[foundLineLength];
pos += foundLineLength + 1;
if (ch == '\r')
{
string result = _s.Substring(_pos, i - _pos);
_pos = i + 1;
if (ch == '\r' && _pos < _length && _s[_pos] == '\n')
if ((uint)pos < s.Length && s[pos] == '\n')
nietras marked this conversation as resolved.
Show resolved Hide resolved
{
_pos++;
pos++;
}

return result;
}
_pos = pos;

i++;
return result;
}

if (i > _pos)
else
{
string result = _s.Substring(_pos, i - _pos);
_pos = i;
string result = s.Substring(pos);
_pos = s.Length;
return result;
}

return null;
}

#region Task based Async APIs
Expand Down Expand Up @@ -264,5 +271,11 @@ public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) :
new ValueTask<int>(Read(buffer.Span));
#endregion

[DoesNotReturn]
private static void ThrowObjectDisposedException_ReaderClosed()
{
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
}
}
}