Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 04229be

Browse files
committed
Fast ulong output
1 parent 99c9012 commit 04229be

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIterator.cs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,15 +1096,86 @@ private static byte[] CreateNumericBytesScratch()
10961096
return bytes;
10971097
}
10981098

1099-
public void CopyFromNumeric(ulong value)
1099+
public unsafe void CopyFromNumeric(ulong value)
11001100
{
1101+
const byte AsciiDigitStart = (byte)'0';
1102+
1103+
var block = _block;
1104+
if (block == null)
1105+
{
1106+
return;
1107+
}
1108+
1109+
var blockIndex = _index;
1110+
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
1111+
var start = block.DataFixedPtr + blockIndex;
1112+
1113+
if (value < 10)
1114+
{
1115+
if (bytesLeftInBlock < 1)
1116+
{
1117+
goto overflow;
1118+
}
1119+
_index = blockIndex + 1;
1120+
block.End = blockIndex + 1;
1121+
1122+
*(start) = (byte)(((uint)value) + AsciiDigitStart);
1123+
}
1124+
else if (value < 100)
1125+
{
1126+
if (bytesLeftInBlock < 2)
1127+
{
1128+
goto overflow;
1129+
}
1130+
_index = blockIndex + 2;
1131+
block.End = blockIndex + 2;
1132+
1133+
var val = (uint)value;
1134+
var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028
1135+
1136+
*(start) = (byte)(tens + AsciiDigitStart);
1137+
*(start + 1) = (byte)(val - (tens * 10) + AsciiDigitStart);
1138+
}
1139+
else if (value < 1000)
1140+
{
1141+
if (bytesLeftInBlock < 3)
1142+
{
1143+
goto overflow;
1144+
}
1145+
_index = blockIndex + 3;
1146+
block.End = blockIndex + 3;
1147+
1148+
var val = (uint)value;
1149+
var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098
1150+
var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028
1151+
1152+
*(start) = (byte)(digit0 + AsciiDigitStart);
1153+
*(start + 1) = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart);
1154+
*(start + 2) = (byte)(val - (digits01 * 10) + AsciiDigitStart);
1155+
}
1156+
else
1157+
{
1158+
goto overflow;
1159+
}
1160+
1161+
return;
1162+
1163+
overflow:
1164+
CopyFromNumericOverflow(value);
1165+
}
1166+
1167+
[MethodImpl(MethodImplOptions.NoInlining)]
1168+
private unsafe void CopyFromNumericOverflow(ulong value)
1169+
{
1170+
const byte AsciiDigitStart = (byte)'0';
1171+
11011172
var position = _maxULongByteLength;
11021173
var byteBuffer = NumericBytesScratch;
11031174
do
11041175
{
11051176
// Consider using Math.DivRem() if available
11061177
var quotient = value / 10;
1107-
byteBuffer[--position] = (byte)(0x30 + (value - quotient * 10)); // 0x30 = '0'
1178+
byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0'
11081179
value = quotient;
11091180
}
11101181
while (value != 0);

test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
using Microsoft.AspNetCore.Server.Kestrel;
1111
using Microsoft.AspNetCore.Server.Kestrel.Internal;
1212
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
13+
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
1314
using Microsoft.Extensions.Primitives;
1415
using Xunit;
16+
using Microsoft.Net.Http.Headers;
1517

1618
namespace Microsoft.AspNetCore.Server.KestrelTests
1719
{
@@ -151,6 +153,86 @@ public void ThrowsWhenClearingHeadersAfterReadOnlyIsSet()
151153
Assert.Throws<InvalidOperationException>(() => dictionary.Clear());
152154
}
153155

156+
[Fact]
157+
public void CorrectContentLengthsOutput()
158+
{
159+
using(var pool = new MemoryPool())
160+
{
161+
var block = pool.Lease();
162+
try
163+
{
164+
for (var i = 0u; i <= 9u; i++)
165+
{
166+
block.Reset();
167+
var iter = new MemoryPoolIterator(block);
168+
iter.CopyFromNumeric(i);
169+
170+
Assert.Equal(block.Array[block.Start], (byte)(i + '0'));
171+
Assert.Equal(block.End, block.Start + 1);
172+
Assert.Equal(iter.Index, block.End);
173+
}
174+
for (var i = 10u; i <= 99u; i++)
175+
{
176+
block.Reset();
177+
var iter = new MemoryPoolIterator(block);
178+
iter.CopyFromNumeric(i);
179+
180+
Assert.Equal(block.Array[block.Start], (byte)((i / 10) + '0'));
181+
Assert.Equal(block.Array[block.Start + 1], (byte)((i % 10) + '0'));
182+
183+
Assert.Equal(block.End, block.Start + 2);
184+
Assert.Equal(iter.Index, block.End);
185+
}
186+
for (var i = 100u; i <= 999u; i++)
187+
{
188+
block.Reset();
189+
var iter = new MemoryPoolIterator(block);
190+
iter.CopyFromNumeric(i);
191+
192+
Assert.Equal(block.Array[block.Start], (byte)((i / 100) + '0'));
193+
Assert.Equal(block.Array[block.Start + 1], (byte)(((i % 100) / 10) + '0'));
194+
Assert.Equal(block.Array[block.Start + 2], (byte)((i % 10) + '0'));
195+
196+
Assert.Equal(block.End, block.Start + 3);
197+
Assert.Equal(iter.Index, block.End);
198+
}
199+
for (var i = 1000u; i <= 9999u; i++)
200+
{
201+
block.Reset();
202+
var iter = new MemoryPoolIterator(block);
203+
iter.CopyFromNumeric(i);
204+
205+
Assert.Equal(block.Array[block.Start], (byte)((i / 1000) + '0'));
206+
Assert.Equal(block.Array[block.Start + 1], (byte)(((i % 1000) / 100) + '0'));
207+
Assert.Equal(block.Array[block.Start + 2], (byte)(((i % 100) / 10) + '0'));
208+
Assert.Equal(block.Array[block.Start + 3], (byte)((i % 10) + '0'));
209+
210+
Assert.Equal(block.End, block.Start + 4);
211+
Assert.Equal(iter.Index, block.End);
212+
}
213+
{
214+
block.Reset();
215+
var iter = new MemoryPoolIterator(block);
216+
iter.CopyFromNumeric(ulong.MaxValue);
217+
218+
var outputBytes = Encoding.ASCII.GetBytes(ulong.MaxValue.ToString("0"));
219+
220+
for (var i = 0; i < outputBytes.Length; i++)
221+
{
222+
Assert.Equal(block.Array[block.Start + i], outputBytes[i]);
223+
}
224+
225+
Assert.Equal(block.End, block.Start + outputBytes.Length);
226+
Assert.Equal(iter.Index, block.End);
227+
}
228+
}
229+
finally
230+
{
231+
pool.Return(block);
232+
}
233+
}
234+
}
235+
154236
[Theory]
155237
[MemberData(nameof(BadContentLengths))]
156238
public void ThrowsWhenAddingContentLengthWithNonNumericValue(string contentLength)

0 commit comments

Comments
 (0)