Skip to content

Commit 519fe34

Browse files
committed
Replace MimeReader calls to IndexOf('\n') with new EndOfLine() method
This was an attempt at overcoming the performance degradation of using Span<byte> in place of raw pointers in MimeReader, specifically for the .NET 4.x frameworks. Unfortunately, this new custom implementation (based on the previous pointer-based optimizations I had written) is actually slightly slower than Span<byte>.indexOf('\n'). Need to figure out a better solution...
1 parent 9c51629 commit 519fe34

File tree

2 files changed

+104
-16
lines changed

2 files changed

+104
-16
lines changed

MimeKit/MimeReader.cs

+18-16
Original file line numberDiff line numberDiff line change
@@ -1209,14 +1209,14 @@ static bool IsMboxMarker (byte[] buffer, int startIndex = 0, bool allowMunged =
12091209

12101210
bool StepMboxMarkerStart (ref bool midline)
12111211
{
1212-
var span = input.AsSpan ();
1212+
var span = input.AsSpan (0, inputEnd + 1);
12131213
int index = inputIndex;
12141214

12151215
input[inputEnd] = (byte) '\n';
12161216

12171217
if (midline) {
12181218
// we're in the middle of a line, so we need to scan for the end of the line
1219-
index = span.Slice (index).IndexOf ((byte) '\n') + index;
1219+
index = span.Slice (index).EndOfLine () + index;
12201220

12211221
if (index == inputEnd) {
12221222
// we don't have enough input data
@@ -1238,7 +1238,7 @@ bool StepMboxMarkerStart (ref bool midline)
12381238
}
12391239

12401240
// scan for the end of the line
1241-
index = span.Slice (index).IndexOf ((byte) '\n') + index;
1241+
index = span.Slice (index).EndOfLine () + index;
12421242

12431243
if (index == inputEnd) {
12441244
// we don't have enough data to check for a From line
@@ -1261,7 +1261,7 @@ bool StepMboxMarker (out int count)
12611261
input[inputEnd] = (byte) '\n';
12621262

12631263
// scan for the end of the line
1264-
count = input.AsSpan (inputIndex).IndexOf ((byte) '\n');
1264+
count = input.AsSpan (inputIndex, (inputEnd + 1) - inputIndex).EndOfLine ();
12651265

12661266
int index = inputIndex + count;
12671267

@@ -1435,13 +1435,14 @@ void StepHeaderField (int headerFieldLength)
14351435

14361436
bool StepHeaderValue (ref bool midline)
14371437
{
1438+
var span = input.AsSpan (0, inputEnd + 1);
14381439
int index = inputIndex;
14391440
int nread;
14401441

14411442
input[inputEnd] = (byte) '\n';
14421443

14431444
while (index < inputEnd && (midline || IsBlank (input[index]))) {
1444-
int count = input.AsSpan (index).IndexOf ((byte) '\n');
1445+
int count = span.Slice (index).EndOfLine ();
14451446

14461447
index += count;
14471448

@@ -1499,8 +1500,8 @@ bool TryCheckBoundaryWithinHeaderBlock ()
14991500
{
15001501
input[inputEnd] = (byte) '\n';
15011502

1502-
var span = input.AsSpan (inputIndex);
1503-
int length = span.IndexOf ((byte) '\n');
1503+
var span = input.AsSpan (inputIndex, (inputEnd + 1) - inputIndex);
1504+
int length = span.EndOfLine ();
15041505

15051506
if (inputIndex + length == inputEnd)
15061507
return false;
@@ -1710,7 +1711,7 @@ bool InnerSkipLine (bool consumeNewLine)
17101711
{
17111712
input[inputEnd] = (byte) '\n';
17121713

1713-
int index = input.AsSpan (inputIndex).IndexOf ((byte) '\n') + inputIndex;
1714+
int index = input.AsSpan (inputIndex, (inputEnd + 1) - inputIndex).EndOfLine () + inputIndex;
17141715

17151716
if (index < inputEnd) {
17161717
inputIndex = index;
@@ -1858,8 +1859,8 @@ BoundaryType CheckBoundary ()
18581859
{
18591860
input[inputEnd] = (byte) '\n';
18601861

1861-
var span = input.AsSpan (inputIndex);
1862-
int length = span.IndexOf ((byte) '\n');
1862+
var span = input.AsSpan (inputIndex, (inputEnd + 1) - inputIndex);
1863+
int length = span.EndOfLine ();
18631864
var line = span.Slice (0, length);
18641865

18651866
return CheckBoundary (inputIndex, line);
@@ -1871,8 +1872,8 @@ bool FoundImmediateBoundary (bool final)
18711872

18721873
input[inputEnd] = (byte) '\n';
18731874

1874-
var span = input.AsSpan (inputIndex);
1875-
int length = span.IndexOf ((byte) '\n');
1875+
var span = input.AsSpan (inputIndex, (inputEnd + 1) - inputIndex);
1876+
int length = span.EndOfLine ();
18761877
var line = span.Slice (0, length);
18771878

18781879
return IsBoundary (line, bounds[0].Marker, boundaryLength);
@@ -1907,6 +1908,7 @@ static bool IsMessagePart (ContentType contentType, ContentEncoding? encoding)
19071908

19081909
void ScanContent (ref int nleft, ref bool midline, ref bool[] formats)
19091910
{
1911+
var span = input.AsSpan (0, inputEnd + 1);
19101912
int length = inputEnd - inputIndex;
19111913
int startIndex = inputIndex;
19121914
int index = inputIndex;
@@ -1917,13 +1919,13 @@ void ScanContent (ref int nleft, ref bool midline, ref bool[] formats)
19171919
input[inputEnd] = (byte) '\n';
19181920

19191921
while (index < inputEnd) {
1920-
var span = input.AsSpan (index);
1922+
var slice = span.Slice (index);
19211923

1922-
length = span.IndexOf ((byte) '\n');
1924+
length = slice.EndOfLine ();
19231925
index += length;
19241926

19251927
if (index < inputEnd) {
1926-
var line = span.Slice (0, length);
1928+
var line = slice.Slice (0, length);
19271929

19281930
if ((boundary = CheckBoundary (startIndex, line)) != BoundaryType.None)
19291931
break;
@@ -1946,7 +1948,7 @@ void ScanContent (ref int nleft, ref bool midline, ref bool[] formats)
19461948
break;
19471949
}
19481950

1949-
var line = span.Slice (0, length);
1951+
var line = slice.Slice (0, length);
19501952

19511953
if ((boundary = CheckBoundary (startIndex, line)) != BoundaryType.None)
19521954
break;

MimeKit/Utils/SpanExtensions.cs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// SpanExtensions.cs
3+
//
4+
// Author: Jeffrey Stedfast <[email protected]>
5+
//
6+
// Copyright (c) 2013-2025 .NET Foundation and Contributors
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall be included in
16+
// all copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
//
26+
27+
using System;
28+
using System.Runtime.CompilerServices;
29+
using System.Runtime.InteropServices;
30+
31+
namespace MimeKit.Utils {
32+
static class SpanExtensions
33+
{
34+
#if NET8_0_OR_GREATER
35+
[MethodImpl (MethodImplOptions.AggressiveInlining)]
36+
public static int EndOfLine (this Span<byte> span)
37+
{
38+
// Note: Span<byte>.IndexOf(byte) is insanely fast in .NET >= 8.0, so use it.
39+
return span.IndexOf ((byte) '\n');
40+
}
41+
#else
42+
[MethodImpl (MethodImplOptions.AggressiveInlining)]
43+
public unsafe static int EndOfLine (this Span<byte> span)
44+
{
45+
fixed (byte* inbuf = &MemoryMarshal.GetReference (span)) {
46+
byte* inptr = inbuf;
47+
48+
// scan for a linefeed character until we are 4-byte aligned.
49+
switch (((long) inptr) & 0x03) {
50+
case 1:
51+
if (*inptr == (byte) '\n')
52+
return (int) (inptr - inbuf);
53+
inptr++;
54+
goto case 2;
55+
case 2:
56+
if (*inptr == (byte) '\n')
57+
return (int) (inptr - inbuf);
58+
inptr++;
59+
goto case 3;
60+
case 3:
61+
if (*inptr == (byte) '\n')
62+
return (int) (inptr - inbuf);
63+
inptr++;
64+
break;
65+
}
66+
67+
// -funroll-loops, yippee ki-yay.
68+
do {
69+
uint mask = *((uint*) inptr) ^ 0x0A0A0A0A;
70+
mask = ((mask - 0x01010101) & (~mask & 0x80808080));
71+
72+
if (mask != 0)
73+
break;
74+
75+
inptr += 4;
76+
} while (true);
77+
78+
while (*inptr != (byte) '\n')
79+
inptr++;
80+
81+
return (int) (inptr - inbuf);
82+
}
83+
}
84+
#endif
85+
}
86+
}

0 commit comments

Comments
 (0)