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

Commit 9d3760e

Browse files
committed
Reduce GetString allocs and conversions
1 parent a3a49d2 commit 9d3760e

File tree

3 files changed

+112
-18
lines changed

3 files changed

+112
-18
lines changed

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ private bool TakeStartLine(SocketInput input)
629629
{
630630
return false;
631631
}
632-
var method = begin.GetString(scan);
632+
var method = begin.GetAsciiString(scan);
633633

634634
scan.Take();
635635
begin = scan;
@@ -653,7 +653,7 @@ private bool TakeStartLine(SocketInput input)
653653
{
654654
return false;
655655
}
656-
queryString = begin.GetString(scan);
656+
queryString = begin.GetAsciiString(scan);
657657
}
658658

659659
scan.Take();
@@ -662,20 +662,24 @@ private bool TakeStartLine(SocketInput input)
662662
{
663663
return false;
664664
}
665-
var httpVersion = begin.GetString(scan);
665+
var httpVersion = begin.GetAsciiString(scan);
666666

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

673+
string requestUrlPath;
673674
if (needDecode)
674675
{
675676
pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd);
677+
requestUrlPath = pathBegin.GetUtf8String(pathEnd);
678+
}
679+
else
680+
{
681+
requestUrlPath = pathBegin.GetAsciiString(pathEnd);
676682
}
677-
678-
var requestUrlPath = pathBegin.GetString(pathEnd);
679683

680684
consumed = scan;
681685
Method = method;
@@ -691,11 +695,6 @@ private bool TakeStartLine(SocketInput input)
691695
}
692696
}
693697

694-
static string GetString(ArraySegment<byte> range, int startIndex, int endIndex)
695-
{
696-
return Encoding.UTF8.GetString(range.Array, range.Offset + startIndex, endIndex - startIndex);
697-
}
698-
699698
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
700699
{
701700
var scan = input.ConsumingStart();
@@ -787,7 +786,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
787786
}
788787

789788
var name = beginName.GetArraySegment(endName);
790-
var value = beginValue.GetString(endValue);
789+
var value = beginValue.GetAsciiString(endValue);
791790
if (wrapping)
792791
{
793792
value = value.Replace("\r\n", " ");

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

Lines changed: 98 additions & 3 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 _maxStackAllocBytes = 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.
@@ -23,6 +24,7 @@ public struct MemoryPoolIterator2
2324
private static Vector<byte> _dotIndex = new Vector<byte>(Enumerable.Range(0, Vector<byte>.Count).Select(x => (byte)-x).ToArray());
2425

2526
private static Encoding _utf8 = Encoding.UTF8;
27+
private static Encoding _ascii = Encoding.ASCII;
2628

2729
private MemoryPoolBlock2 _block;
2830
private int _index;
@@ -488,7 +490,101 @@ public int GetLength(MemoryPoolIterator2 end)
488490
}
489491
}
490492

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

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(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(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(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(resultEnd);
169169
Assert.Equal(raw, result);
170170
}
171171
}

0 commit comments

Comments
 (0)