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

Commit fee6712

Browse files
committed
Reduce GetString allocs and conversions
1 parent 0bfc97e commit fee6712

File tree

4 files changed

+143
-28
lines changed

4 files changed

+143
-28
lines changed

src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ private bool TakeStartLine(SocketInput input)
627627
{
628628
return false;
629629
}
630-
var method = begin.GetString(scan);
630+
var method = begin.GetAsciiString(ref scan);
631631

632632
scan.Take();
633633
begin = scan;
@@ -651,7 +651,7 @@ private bool TakeStartLine(SocketInput input)
651651
{
652652
return false;
653653
}
654-
queryString = begin.GetString(scan);
654+
queryString = begin.GetAsciiString(ref scan);
655655
}
656656

657657
scan.Take();
@@ -660,20 +660,24 @@ private bool TakeStartLine(SocketInput input)
660660
{
661661
return false;
662662
}
663-
var httpVersion = begin.GetString(scan);
663+
var httpVersion = begin.GetAsciiString(ref scan);
664664

665665
scan.Take();
666666
if (scan.Take() != '\n')
667667
{
668668
return false;
669669
}
670670

671+
string requestUrlPath;
671672
if (needDecode)
672673
{
673674
pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd);
675+
requestUrlPath = pathBegin.GetUtf8String(ref pathEnd);
676+
}
677+
else
678+
{
679+
requestUrlPath = pathBegin.GetAsciiString(ref pathEnd);
674680
}
675-
676-
var requestUrlPath = pathBegin.GetString(pathEnd);
677681

678682
consumed = scan;
679683
Method = method;
@@ -689,11 +693,6 @@ private bool TakeStartLine(SocketInput input)
689693
}
690694
}
691695

692-
static string GetString(ArraySegment<byte> range, int startIndex, int endIndex)
693-
{
694-
return Encoding.UTF8.GetString(range.Array, range.Offset + startIndex, endIndex - startIndex);
695-
}
696-
697696
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
698697
{
699698
var scan = input.ConsumingStart();
@@ -784,8 +783,8 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
784783
continue;
785784
}
786785

787-
var name = beginName.GetArraySegment(endName);
788-
var value = beginValue.GetString(endValue);
786+
var name = beginName.GetArraySegment(ref endName);
787+
var value = beginValue.GetAsciiString(ref endValue);
789788
if (wrapping)
790789
{
791790
value = value.Replace("\r\n", " ");

src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2.cs

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
1010
{
1111
public struct MemoryPoolIterator2
1212
{
13+
private const int _maxHeaderStackLength = 16384;
1314
/// <summary>
1415
/// Array of "minus one" bytes of the length of SIMD operations on the current hardware. Used as an argument in the
1516
/// vector dot product that counts matching character occurrence.
@@ -459,7 +460,7 @@ public bool Put(byte data)
459460
}
460461
}
461462

462-
public int GetLength(MemoryPoolIterator2 end)
463+
public int GetLength(ref MemoryPoolIterator2 end)
463464
{
464465
if (IsDefault || end.IsDefault)
465466
{
@@ -488,20 +489,135 @@ public int GetLength(MemoryPoolIterator2 end)
488489
}
489490
}
490491

491-
public string GetString(MemoryPoolIterator2 end)
492+
private static unsafe string SingleBlockAsciiString(byte[] input, int offset, int length)
493+
{
494+
// avoid declaring other local vars, or doing work with stackalloc
495+
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
496+
char* output = stackalloc char[length];
497+
498+
return SingleBlockAsciiIter(output, input, offset, length);
499+
}
500+
501+
private static unsafe string SingleBlockAsciiIter(char* output, byte[] input, int offset, int length)
502+
{
503+
for (var i = 0; i < length; i++)
504+
{
505+
output[i] = (char)input[i + offset];
506+
}
507+
return new string(output, 0, length);
508+
}
509+
510+
private static unsafe string MultiBlockAsciiString(MemoryPoolBlock2 startBlock, ref MemoryPoolIterator2 end, int inputOffset, int length)
511+
{
512+
// avoid declaring other local vars, or doing work with stackalloc
513+
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
514+
char* output = stackalloc char[length];
515+
516+
return MultiBlockAsciiIter(output, startBlock, ref end, inputOffset, length);
517+
}
518+
519+
private static unsafe string MultiBlockAsciiIter(char* output, MemoryPoolBlock2 startBlock, ref MemoryPoolIterator2 end, int inputOffset, int length)
520+
{
521+
var outputOffset = 0;
522+
var block = startBlock;
523+
var remaining = length;
524+
525+
while(true)
526+
{
527+
int following = (block != end._block ? block.End : end._index) - inputOffset;
528+
529+
if (following > 0)
530+
{
531+
var input = block.Array;
532+
for (var i = 0; i < following; i++)
533+
{
534+
output[i + outputOffset] = (char)input[i + inputOffset];
535+
}
536+
537+
remaining -= following;
538+
outputOffset += following;
539+
}
540+
541+
if (remaining == 0)
542+
{
543+
return new string(output, 0, length);
544+
}
545+
546+
block = block.Next;
547+
inputOffset = block.Start;
548+
}
549+
}
550+
551+
public string GetAsciiStringHeap(MemoryPoolBlock2 startBlock, ref MemoryPoolIterator2 end, int inputOffset, int length)
552+
{
553+
var output = new char[length];
554+
555+
var outputOffset = 0;
556+
var block = startBlock;
557+
var remaining = length;
558+
559+
while (true)
560+
{
561+
int following = (block != end._block ? block.End : end._index) - inputOffset;
562+
563+
if (following > 0)
564+
{
565+
var input = block.Array;
566+
for (var i = 0; i < following; i++)
567+
{
568+
output[i + outputOffset] = (char)input[i + inputOffset];
569+
}
570+
571+
remaining -= following;
572+
outputOffset += following;
573+
}
574+
575+
if (remaining == 0)
576+
{
577+
return new string(output, 0, length);
578+
}
579+
580+
block = block.Next;
581+
inputOffset = block.Start;
582+
}
583+
}
584+
585+
public string GetAsciiString(ref MemoryPoolIterator2 end)
492586
{
493587
if (IsDefault || end.IsDefault)
494588
{
495589
return default(string);
496590
}
591+
592+
var length = GetLength(ref end);
593+
594+
if (length > _maxHeaderStackLength)
595+
{
596+
return GetAsciiStringHeap(_block, ref end, _index, length);
597+
}
598+
497599
if (end._block == _block)
498600
{
499-
return _utf8.GetString(_block.Array, _index, end._index - _index);
601+
return SingleBlockAsciiString(_block.Array, _index, length);
500602
}
501603

604+
return MultiBlockAsciiString(_block, ref end, _index, length);
605+
}
606+
607+
public string GetUtf8String(ref MemoryPoolIterator2 end)
608+
{
609+
if (IsDefault || end.IsDefault)
610+
{
611+
return default(string);
612+
}
613+
if (end._block == _block)
614+
{
615+
return _utf8.GetString(_block.Array, _index, end._index - _index);
616+
}
617+
502618
var decoder = _utf8.GetDecoder();
619+
var length = GetLength(ref end);
503620

504-
var length = GetLength(end);
505621
var charLength = length * 2;
506622
var chars = new char[charLength];
507623
var charIndex = 0;
@@ -566,7 +682,7 @@ public string GetString(MemoryPoolIterator2 end)
566682
}
567683
}
568684

569-
public ArraySegment<byte> GetArraySegment(MemoryPoolIterator2 end)
685+
public ArraySegment<byte> GetArraySegment(ref MemoryPoolIterator2 end)
570686
{
571687
if (IsDefault || end.IsDefault)
572688
{
@@ -577,7 +693,7 @@ public ArraySegment<byte> GetArraySegment(MemoryPoolIterator2 end)
577693
return new ArraySegment<byte>(_block.Array, _index, end._index - _index);
578694
}
579695

580-
var length = GetLength(end);
696+
var length = GetLength(ref end);
581697
var array = new byte[length];
582698
CopyTo(array, 0, length, out length);
583699
return new ArraySegment<byte>(array, 0, length);

test/Microsoft.AspNet.Server.KestrelTests/MemoryPoolBlock2Tests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ public void SeekWorks()
2121
{
2222
var hit = iterator;
2323
hit.Seek(ch);
24-
Assert.Equal(ch, iterator.GetLength(hit));
24+
Assert.Equal(ch, iterator.GetLength(ref hit));
2525

2626
hit = iterator;
2727
hit.Seek(ch, byte.MaxValue);
28-
Assert.Equal(ch, iterator.GetLength(hit));
28+
Assert.Equal(ch, iterator.GetLength(ref hit));
2929

3030
hit = iterator;
3131
hit.Seek(byte.MaxValue, ch);
32-
Assert.Equal(ch, iterator.GetLength(hit));
32+
Assert.Equal(ch, iterator.GetLength(ref hit));
3333
}
3434
}
3535
}
@@ -72,9 +72,9 @@ private void TestAllLengths(MemoryPoolBlock2 block, int lengths)
7272
{
7373
var first = block.GetIterator().Add(firstIndex);
7474
var last = block.GetIterator().Add(lastIndex);
75-
Assert.Equal(firstIndex, block.GetIterator().GetLength(first));
76-
Assert.Equal(lastIndex, block.GetIterator().GetLength(last));
77-
Assert.Equal(lastIndex - firstIndex, first.GetLength(last));
75+
Assert.Equal(firstIndex, block.GetIterator().GetLength(ref first));
76+
Assert.Equal(lastIndex, block.GetIterator().GetLength(ref last));
77+
Assert.Equal(lastIndex - firstIndex, first.GetLength(ref last));
7878
}
7979
}
8080
}

test/Microsoft.AspNet.Server.KestrelTests/UrlPathDecoder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public void DecodeWithBoundary(string raw, int rawLength, string expect, int exp
115115
var end = GetIterator(begin, rawLength);
116116

117117
var end2 = UrlPathDecoder.Unescape(begin, end);
118-
var result = begin.GetString(end2);
118+
var result = begin.GetUtf8String(ref end2);
119119

120120
Assert.Equal(expectLength, result.Length);
121121
Assert.Equal(expect, result);
@@ -147,7 +147,7 @@ private void PositiveAssert(string raw, string expect)
147147
var end = GetIterator(begin, raw.Length);
148148

149149
var result = UrlPathDecoder.Unescape(begin, end);
150-
Assert.Equal(expect, begin.GetString(result));
150+
Assert.Equal(expect, begin.GetUtf8String(ref result));
151151
}
152152

153153
private void PositiveAssert(string raw)
@@ -156,7 +156,7 @@ private void PositiveAssert(string raw)
156156
var end = GetIterator(begin, raw.Length);
157157

158158
var result = UrlPathDecoder.Unescape(begin, end);
159-
Assert.NotEqual(raw.Length, begin.GetString(result).Length);
159+
Assert.NotEqual(raw.Length, begin.GetUtf8String(ref result).Length);
160160
}
161161

162162
private void NegativeAssert(string raw)
@@ -165,7 +165,7 @@ private void NegativeAssert(string raw)
165165
var end = GetIterator(begin, raw.Length);
166166

167167
var resultEnd = UrlPathDecoder.Unescape(begin, end);
168-
var result = begin.GetString(resultEnd);
168+
var result = begin.GetUtf8String(ref resultEnd);
169169
Assert.Equal(raw, result);
170170
}
171171
}

0 commit comments

Comments
 (0)