diff --git a/src/System.IO/tests/StreamWriter/StreamWriter.WriteTests.netcoreapp.cs b/src/System.IO/tests/StreamWriter/StreamWriter.WriteTests.netcoreapp.cs new file mode 100644 index 000000000000..eec8dedabcd5 --- /dev/null +++ b/src/System.IO/tests/StreamWriter/StreamWriter.WriteTests.netcoreapp.cs @@ -0,0 +1,54 @@ +// 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 Xunit; + +namespace System.IO.Tests +{ + public partial class WriteTests + { + [Fact] + public void WriteReadOnlySpanTest() + { + char[] chArr = TestDataProvider.CharData; + ReadOnlySpan readSpan = new ReadOnlySpan(chArr); + + Stream ms = CreateStream(); + StreamWriter sw = new StreamWriter(ms); + StreamReader sr; + + sw.Write(readSpan); + sw.Flush(); + ms.Position = 0; + sr = new StreamReader(ms); + + for (int i = 0; i < chArr.Length; i++) + { + Assert.Equal((int)chArr[i], sr.Read()); + } + ms.Dispose(); + } + + [Fact] + public void WriteLineReadOnlySpanTest() + { + char[] chArr = TestDataProvider.CharData; + ReadOnlySpan readSpan = new ReadOnlySpan(chArr); + + Stream ms = CreateStream(); + StreamWriter sw = new StreamWriter(ms); + StreamReader sr; + + sw.Write(readSpan); + sw.Flush(); + ms.Position = 0; + sr = new StreamReader(ms); + + string readData = sr.ReadLine(); + Assert.Equal(new string(chArr), readData); + + ms.Dispose(); + } + } +} diff --git a/src/System.IO/tests/System.IO.Tests.csproj b/src/System.IO/tests/System.IO.Tests.csproj index 7273e968b5e4..f4592fd56052 100644 --- a/src/System.IO/tests/System.IO.Tests.csproj +++ b/src/System.IO/tests/System.IO.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/src/System.Runtime.Extensions/src/System/IO/StreamWriter.cs b/src/System.Runtime.Extensions/src/System/IO/StreamWriter.cs index e97070d27ec6..6c12d306596d 100644 --- a/src/System.Runtime.Extensions/src/System/IO/StreamWriter.cs +++ b/src/System.Runtime.Extensions/src/System/IO/StreamWriter.cs @@ -2,9 +2,10 @@ // 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.Buffers; +using System.Diagnostics; using System.Text; using System.Threading.Tasks; -using System.Diagnostics; namespace System.IO { @@ -330,52 +331,13 @@ public override void Write(char[] buffer) return; } - CheckAsyncTaskInProgress(); - - // Threshold of 4 was chosen after running perf tests - if (buffer.Length <= 4) + if (GetType() == typeof(StreamWriter)) { - for (int i = 0; i < buffer.Length; i++) - { - if (_charPos == _charLen) - { - Flush(false, false); - } - - Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code."); - _charBuffer[_charPos] = buffer[i]; - _charPos++; - } + WriteCore(new ReadOnlySpan(buffer)); } else { - int count = buffer.Length; - - int index = 0; - while (count > 0) - { - if (_charPos == _charLen) - { - Flush(false, false); - } - - int n = _charLen - _charPos; - if (n > count) - { - n = count; - } - - Debug.Assert(n > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code."); - Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char)); - _charPos += n; - index += n; - count -= n; - } - } - - if (_autoFlush) - { - Flush(true, false); + base.Write(buffer); } } @@ -397,9 +359,35 @@ public override void Write(char[] buffer, int index, int count) { throw new ArgumentException(SR.Argument_InvalidOffLen); } + if (GetType() == typeof(StreamWriter)) + { + WriteCore(new ReadOnlySpan(buffer, index, count)); + } + else + { + base.Write(buffer, index, count); + } + } + + public override void Write(ReadOnlySpan source) + { + if (GetType() == typeof(StreamWriter)) + { + WriteCore(source); + } + else + { + base.Write(source); + } + } + private void WriteCore(ReadOnlySpan source) + { CheckAsyncTaskInProgress(); + int count = source.Length; + int index = 0; + // Threshold of 4 was chosen after running perf tests if (count <= 4) { @@ -411,7 +399,7 @@ public override void Write(char[] buffer, int index, int count) } Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code."); - _charBuffer[_charPos] = buffer[index]; + _charBuffer[_charPos] = source[index]; _charPos++; index++; count--; @@ -433,7 +421,7 @@ public override void Write(char[] buffer, int index, int count) } Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code."); - Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char)); + source.Slice(index, n).CopyTo(new Span(_charBuffer, _charPos, n)); _charPos += n; index += n; count -= n; @@ -453,52 +441,13 @@ public override void Write(string value) return; } - CheckAsyncTaskInProgress(); - - int count = value.Length; - - // Threshold of 4 was chosen after running perf tests - if (count <= 4) + if (GetType() == typeof(StreamWriter)) { - for (int i = 0; i < count; i++) - { - if (_charPos == _charLen) - { - Flush(false, false); - } - - Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code."); - _charBuffer[_charPos] = value[i]; - _charPos++; - } + WriteCore(value.AsReadOnlySpan()); } else { - int index = 0; - while (count > 0) - { - if (_charPos == _charLen) - { - Flush(false, false); - } - - int n = _charLen - _charPos; - if (n > count) - { - n = count; - } - - Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code."); - value.CopyTo(index, _charBuffer, _charPos, n); - _charPos += n; - index += n; - count -= n; - } - } - - if (_autoFlush) - { - Flush(true, false); + base.Write(value); } } @@ -513,6 +462,23 @@ public override void WriteLine(string value) value = String.Empty; } + if (GetType() == typeof(StreamWriter)) + { + WriteLineCore(value.AsReadOnlySpan()); + } + else + { + base.WriteLine(value); + } + } + + public override void WriteLine(ReadOnlySpan source) + { + WriteLineCore(source); + } + + private void WriteLineCore(ReadOnlySpan value) + { CheckAsyncTaskInProgress(); int count = value.Length; @@ -531,7 +497,7 @@ public override void WriteLine(string value) } Debug.Assert(n > 0, "StreamWriter::WriteLine(String) isn't making progress! This is most likely a race condition in user code."); - value.CopyTo(index, _charBuffer, _charPos, n); + value.Slice(index, n).CopyTo(new Span(_charBuffer, _charPos, n)); _charPos += n; index += n; count -= n; diff --git a/src/System.Runtime.Extensions/tests/Performance/Perf.StreamWriter.cs b/src/System.Runtime.Extensions/tests/Performance/Perf.StreamWriter.cs index 3660f8464bee..466e3537aa64 100644 --- a/src/System.Runtime.Extensions/tests/Performance/Perf.StreamWriter.cs +++ b/src/System.Runtime.Extensions/tests/Performance/Perf.StreamWriter.cs @@ -76,6 +76,38 @@ public void WritePartialCharArray(int writeLength) } } + [Benchmark] + [MemberData(nameof(WriteLengthMemberData))] + public void WriteReadOnlySpan(int writeLength) + { + char[] buffer = new string('a', writeLength + 10).ToCharArray(); + ReadOnlySpan span = new ReadOnlySpan(buffer); + + int innerIterations = MemoryStreamSize / writeLength; + int outerIteration = TotalWriteCount / innerIterations; + using (var stream = new MemoryStream(MemoryStreamSize)) + { + using (var writer = new StreamWriter(stream, new UTF8Encoding(false, true), DefaultStreamWriterBufferSize, true)) + { + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < outerIteration; i++) + { + for (int j = 0; j < innerIterations; j++) + { + writer.Write(span); + } + writer.Flush(); + stream.Position = 0; + } + } + } + } + } + } + [Benchmark] [MemberData(nameof(WriteLengthMemberData))] public void WriteString(int writeLength)