-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
585: Optimize Razor output rendering to avoid LOH allocations
Instead of creating a string from the StringWriter (_tempWriter) we will copy the underlying buffer in chunks to the output text buffer. For large pages this both reduces the total allocations to 1KB as well as avoid getting into the large object heap.
- Loading branch information
Yishai Galatzer
committed
Jul 14, 2014
1 parent
a3dcd16
commit 3fe0d34
Showing
5 changed files
with
159 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace System.Web.WebPages | ||
{ | ||
internal static class StringWriterExtensions | ||
{ | ||
public const int BufferSize = 1024; | ||
|
||
// Used to copy data from a string writer to avoid allocating the full string | ||
// which can end up on LOH (and cause memory fragmentation). | ||
public static void CopyTo(this StringWriter input, TextWriter output) | ||
{ | ||
StringBuilder builder = input.GetStringBuilder(); | ||
|
||
int remainingChars = builder.Length; | ||
int bufferSize = Math.Min(builder.Length, BufferSize); | ||
|
||
char[] buffer = new char[bufferSize]; | ||
int currentPosition = 0; | ||
|
||
while (remainingChars > 0) | ||
{ | ||
int copyLen = Math.Min(bufferSize, remainingChars); | ||
|
||
builder.CopyTo(currentPosition, buffer, 0, copyLen); | ||
|
||
output.Write(buffer, 0, copyLen); | ||
|
||
currentPosition += copyLen; | ||
remainingChars -= copyLen; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
test/System.Web.WebPages.Test/Extensions/StringWriterExtensionsTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using Microsoft.TestCommon; | ||
using Moq; | ||
|
||
namespace System.Web.WebPages.Test | ||
{ | ||
public class StringWriterExtensionsTest | ||
{ | ||
[Fact] | ||
public void CopiesResult() | ||
{ | ||
// Note that a preable is not expected on the generated stream. | ||
string text = "Hello world"; | ||
Byte[] textInBytes = Encoding.UTF8.GetBytes(text); | ||
string outputText; | ||
|
||
Byte[] buffer = new Byte[1024]; | ||
|
||
using (MemoryStream stream = new MemoryStream(buffer)) | ||
using (StringWriter writer = new StringWriter()) | ||
using (StreamWriter outputWriter = new StreamWriter(stream)) | ||
{ | ||
writer.Write(text); | ||
writer.CopyTo(outputWriter); | ||
|
||
outputText = writer.ToString(); | ||
} | ||
|
||
Assert.Equal(text, outputText, StringComparer.Ordinal); | ||
|
||
for (int i = 0; i < textInBytes.Length; i++) | ||
{ | ||
Assert.Equal(textInBytes[i], buffer[i]); | ||
} | ||
} | ||
|
||
[Theory] | ||
[InlineData(1)] | ||
[InlineData(1023)] | ||
[InlineData(1024)] | ||
[InlineData(1025)] | ||
[InlineData(20000)] | ||
[InlineData(100000)] | ||
public void OnlyUsesBufferUpToSize(int count) | ||
{ | ||
string text = new string('a', count); | ||
Byte[] textInBytes = Encoding.UTF8.GetBytes(text); | ||
|
||
Mock<StreamWriter> mock; | ||
|
||
Byte[] buffer = new Byte[textInBytes.Length + 100]; | ||
|
||
using (MemoryStream stream = new MemoryStream(buffer)) | ||
{ | ||
StringWriter writer = new StringWriter(); | ||
|
||
mock = new Mock<StreamWriter>(MockBehavior.Strict, stream) { CallBase = true }; | ||
mock.Setup(sw | ||
=> sw.Write(It.IsAny<char[]>(), | ||
It.IsAny<int>(), | ||
It.Is<int>(c => c == StringWriterExtensions.BufferSize || | ||
c == textInBytes.Length % StringWriterExtensions.BufferSize))). | ||
Verifiable(); | ||
|
||
StreamWriter outputWriter = mock.Object; | ||
writer.Write(text); | ||
writer.CopyTo(outputWriter); | ||
|
||
mock.Verify(); | ||
} | ||
} | ||
|
||
[Theory] | ||
[InlineData(1)] | ||
[InlineData(1023/7)] | ||
[InlineData(1024/7)] | ||
[InlineData(1025/7)] | ||
[InlineData(20000/7)] | ||
[InlineData(100000/7)] | ||
public void ProperlyCopiesLargeSetsOfText(int count) | ||
{ | ||
// The char א turns into a two byte sequence so we end up with a | ||
// 7 byte sequence that is not a divider or 1024. | ||
string text = string.Join(string.Empty, Enumerable.Repeat("abcdeא", count)); | ||
|
||
Byte[] textInBytes = Encoding.UTF8.GetBytes(text); | ||
string outputText; | ||
|
||
Byte[] buffer = new Byte[textInBytes.Length + 100]; | ||
|
||
using (MemoryStream stream = new MemoryStream(buffer)) | ||
using (StringWriter writer = new StringWriter()) | ||
{ | ||
using (StreamWriter outputWriter = new StreamWriter(stream)) | ||
{ | ||
writer.Write(text); | ||
writer.CopyTo(outputWriter); | ||
|
||
outputText = writer.ToString(); | ||
} | ||
} | ||
|
||
Assert.Equal(text, outputText, StringComparer.Ordinal); | ||
|
||
for (int i = 0; i < textInBytes.Length; i++) | ||
{ | ||
Assert.Equal(textInBytes[i], buffer[i]); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters