Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit b98860c

Browse files
mconnewstephentoub
authored andcommitted
Perf improvements to StreamWriter with perf tests
1 parent e411611 commit b98860c

File tree

3 files changed

+219
-41
lines changed

3 files changed

+219
-41
lines changed

src/System.Runtime.Extensions/src/System/IO/StreamWriter.cs

Lines changed: 99 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -334,26 +334,45 @@ public override void Write(char[] buffer)
334334

335335
CheckAsyncTaskInProgress();
336336

337-
int index = 0;
338337
int count = buffer.Length;
339-
while (count > 0)
338+
339+
// Threshold of 4 was chosen after running perf tests
340+
if (count <= 4)
340341
{
341-
if (_charPos == _charLen)
342+
for (int i = 0; i < count; i++)
342343
{
343-
Flush(false, false);
344-
}
344+
if (_charPos == _charLen)
345+
{
346+
Flush(false, false);
347+
}
345348

346-
int n = _charLen - _charPos;
347-
if (n > count)
348-
{
349-
n = count;
349+
Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code.");
350+
_charBuffer[_charPos] = buffer[i];
351+
_charPos++;
350352
}
353+
}
354+
else
355+
{
356+
int index = 0;
357+
while (count > 0)
358+
{
359+
if (_charPos == _charLen)
360+
{
361+
Flush(false, false);
362+
}
351363

352-
Debug.Assert(n > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code.");
353-
Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char));
354-
_charPos += n;
355-
index += n;
356-
count -= n;
364+
int n = _charLen - _charPos;
365+
if (n > count)
366+
{
367+
n = count;
368+
}
369+
370+
Debug.Assert(n > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code.");
371+
Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char));
372+
_charPos += n;
373+
index += n;
374+
count -= n;
375+
}
357376
}
358377

359378
if (_autoFlush)
@@ -383,24 +402,44 @@ public override void Write(char[] buffer, int index, int count)
383402

384403
CheckAsyncTaskInProgress();
385404

386-
while (count > 0)
405+
// Threshold of 4 was chosen after running perf tests
406+
if (count <= 4)
387407
{
388-
if (_charPos == _charLen)
408+
while (count > 0)
389409
{
390-
Flush(false, false);
391-
}
410+
if (_charPos == _charLen)
411+
{
412+
Flush(false, false);
413+
}
392414

393-
int n = _charLen - _charPos;
394-
if (n > count)
395-
{
396-
n = count;
415+
Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race in user code.");
416+
_charBuffer[_charPos] = buffer[index];
417+
_charPos++;
418+
index++;
419+
count--;
397420
}
421+
}
422+
else
423+
{
424+
while (count > 0)
425+
{
426+
if (_charPos == _charLen)
427+
{
428+
Flush(false, false);
429+
}
398430

399-
Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
400-
Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char));
401-
_charPos += n;
402-
index += n;
403-
count -= n;
431+
int n = _charLen - _charPos;
432+
if (n > count)
433+
{
434+
n = count;
435+
}
436+
437+
Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
438+
Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char));
439+
_charPos += n;
440+
index += n;
441+
count -= n;
442+
}
404443
}
405444

406445
if (_autoFlush)
@@ -419,25 +458,44 @@ public override void Write(string value)
419458
CheckAsyncTaskInProgress();
420459

421460
int count = value.Length;
422-
int index = 0;
423-
while (count > 0)
461+
462+
// Threshold of 4 was chosen after running perf tests
463+
if (count <= 4)
424464
{
425-
if (_charPos == _charLen)
465+
for (int i = 0; i < count; i++)
426466
{
427-
Flush(false, false);
428-
}
467+
if (_charPos == _charLen)
468+
{
469+
Flush(false, false);
470+
}
429471

430-
int n = _charLen - _charPos;
431-
if (n > count)
432-
{
433-
n = count;
472+
Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
473+
_charBuffer[_charPos] = value[i];
474+
_charPos++;
434475
}
476+
}
477+
else
478+
{
479+
int index = 0;
480+
while (count > 0)
481+
{
482+
if (_charPos == _charLen)
483+
{
484+
Flush(false, false);
485+
}
435486

436-
Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
437-
value.CopyTo(index, _charBuffer, _charPos, n);
438-
_charPos += n;
439-
index += n;
440-
count -= n;
487+
int n = _charLen - _charPos;
488+
if (n > count)
489+
{
490+
n = count;
491+
}
492+
493+
Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
494+
value.CopyTo(index, _charBuffer, _charPos, n);
495+
_charPos += n;
496+
index += n;
497+
count -= n;
498+
}
441499
}
442500

443501
if (_autoFlush)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Licensed to the.NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Text;
8+
using Microsoft.Xunit.Performance;
9+
using Xunit;
10+
11+
namespace System.IO.Tests
12+
{
13+
public class Perf_StreamWriter
14+
{
15+
private const int MemoryStreamSize = 32768;
16+
private const int TotalWriteCount = 16777216; // 2^24 - should yield around 300ms runs
17+
private const int DefaultStreamWriterBufferSize = 1024; // Same as StreamWriter internal default
18+
19+
[Benchmark]
20+
[MemberData(nameof(WriteMemberData))]
21+
public void WriteCharArray(int writeLength)
22+
{
23+
char[] buffer = new string('a', writeLength).ToCharArray();
24+
int innerIterations = MemoryStreamSize / writeLength;
25+
int outerIteration = TotalWriteCount / innerIterations;
26+
using (var stream = new MemoryStream(MemoryStreamSize))
27+
{
28+
using (var writer = new StreamWriter(stream, new UTF8Encoding(false, true), DefaultStreamWriterBufferSize, true))
29+
{
30+
foreach (var iteration in Benchmark.Iterations)
31+
{
32+
using (iteration.StartMeasurement())
33+
{
34+
for (int i = 0; i < outerIteration; i++)
35+
{
36+
for (int j = 0; j < innerIterations; j++)
37+
{
38+
writer.Write(buffer);
39+
}
40+
writer.Flush();
41+
stream.Position = 0;
42+
}
43+
}
44+
}
45+
}
46+
}
47+
}
48+
49+
[Benchmark]
50+
[MemberData(nameof(WriteMemberData))]
51+
public void WritePartialCharArray(int writeLength)
52+
{
53+
char[] buffer = new string('a', writeLength + 10).ToCharArray();
54+
int innerIterations = MemoryStreamSize / writeLength;
55+
int outerIteration = TotalWriteCount / innerIterations;
56+
using (var stream = new MemoryStream(MemoryStreamSize))
57+
{
58+
using (var writer = new StreamWriter(stream, new UTF8Encoding(false, true), DefaultStreamWriterBufferSize, true))
59+
{
60+
foreach (var iteration in Benchmark.Iterations)
61+
{
62+
using (iteration.StartMeasurement())
63+
{
64+
for (int i = 0; i < outerIteration; i++)
65+
{
66+
for (int j = 0; j < innerIterations; j++)
67+
{
68+
writer.Write(buffer, 10, writeLength);
69+
}
70+
writer.Flush();
71+
stream.Position = 0;
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
[Benchmark]
80+
[MemberData(nameof(WriteMemberData))]
81+
public void WriteString(int writeLength)
82+
{
83+
string value = new string('a', writeLength);
84+
int innerIterations = MemoryStreamSize / writeLength;
85+
int outerIteration = TotalWriteCount / innerIterations;
86+
using (var stream = new MemoryStream(MemoryStreamSize))
87+
{
88+
using (var writer = new StreamWriter(stream, new UTF8Encoding(false, true), DefaultStreamWriterBufferSize, true))
89+
{
90+
foreach (var iteration in Benchmark.Iterations)
91+
{
92+
using (iteration.StartMeasurement())
93+
{
94+
for (int i = 0; i < outerIteration; i++)
95+
{
96+
for (int j = 0; j < innerIterations; j++)
97+
{
98+
writer.Write(value);
99+
}
100+
writer.Flush();
101+
stream.Position = 0;
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
109+
public static IEnumerable<object[]> WriteMemberData()
110+
{
111+
for (int i = 2; i <= 10; i++)
112+
{
113+
yield return new object[] { i };
114+
}
115+
116+
yield return new object[] { 100 };
117+
}
118+
}
119+
}

src/System.Runtime.Extensions/tests/Performance/System.Runtime.Extensions.Performance.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<Compile Include="Perf.Environment.cs" />
1414
<Compile Include="Perf.Path.cs" />
1515
<Compile Include="Perf.Random.cs" />
16+
<Compile Include="Perf.StreamWriter.cs" />
1617
<Compile Include="$(CommonTestPath)\System\PerfUtils.cs">
1718
<Link>Common\System\PerfUtils.cs</Link>
1819
</Compile>

0 commit comments

Comments
 (0)