Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit ad9c493

Browse files
authored
Optimize Utf-8 Integer Formatter for the default case (#25424)
* Optimize Utf-8 Formatter for the default case. * Addressing PR feedback - computing space up front * Applying optimizations and changing to safe code
1 parent d28785d commit ad9c493

10 files changed

+182
-22
lines changed

src/System.Memory/src/System.Memory.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@
5656
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.cs" />
5757
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.cs" />
5858
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.D.cs" />
59+
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.Default.cs" />
5960
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.N.cs" />
6061
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.cs" />
6162
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.D.cs" />
63+
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.Default.cs" />
6264
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.N.cs" />
6365
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.X.cs" />
6466
<Compile Include="System\Buffers\Text\Utf8Formatter\Utf8Formatter.TimeSpan.cs" />

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,31 @@ public static void WriteHexByte(byte value, ref byte buffer, int index)
3131
}
3232

3333
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34-
public static int WriteFractionDigits(long value, int digitCount, ref byte buffer, int index)
34+
public static void WriteFractionDigits(long value, int digitCount, ref byte buffer, int index)
3535
{
3636
for (int i = FractionDigits; i > digitCount; i--)
3737
value /= 10;
3838

39-
return WriteDigits(value, digitCount, ref buffer, index);
39+
WriteDigits(value, digitCount, ref buffer, index);
4040
}
4141

4242
[MethodImpl(MethodImplOptions.AggressiveInlining)]
43-
public static int WriteDigits(long value, int digitCount, ref byte buffer, int index)
43+
public static void WriteDigits(ulong value, Span<byte> buffer)
44+
{
45+
ulong left = value;
46+
47+
for (int i = buffer.Length - 1; i >= 1; i--)
48+
{
49+
left = DivMod(left, 10, out ulong num);
50+
buffer[i] = (byte)('0' + num);
51+
}
52+
53+
Debug.Assert(left < 10);
54+
buffer[0] = (byte)('0' + left);
55+
}
56+
57+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
58+
public static void WriteDigits(long value, int digitCount, ref byte buffer, int index)
4459
{
4560
long left = value;
4661

@@ -50,7 +65,7 @@ public static int WriteDigits(long value, int digitCount, ref byte buffer, int i
5065
Unsafe.Add(ref buffer, index + i) = (byte)('0' + num);
5166
}
5267

53-
return digitCount;
68+
Debug.Assert(left == 0);
5469
}
5570

5671
/// <summary>
@@ -59,7 +74,7 @@ public static int WriteDigits(long value, int digitCount, ref byte buffer, int i
5974
/// you definitely need to deal with numbers larger than long.MaxValue.
6075
/// </summary>
6176
[MethodImpl(MethodImplOptions.AggressiveInlining)]
62-
public static int WriteDigits(ulong value, int digitCount, ref byte buffer, int index)
77+
public static void WriteDigits(ulong value, int digitCount, ref byte buffer, int index)
6378
{
6479
ulong left = value;
6580

@@ -68,8 +83,8 @@ public static int WriteDigits(ulong value, int digitCount, ref byte buffer, int
6883
left = DivMod(left, 10, out ulong num);
6984
Unsafe.Add(ref buffer, index + i) = (byte)('0' + num);
7085
}
71-
72-
return digitCount;
86+
87+
Debug.Assert(left == 0);
7388
}
7489

7590
#endregion UTF-8 Helper methods
@@ -118,6 +133,62 @@ public static int CountDigits(long n)
118133
return digits;
119134
}
120135

136+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
137+
public static int CountDigits(ulong value)
138+
{
139+
int digits = 1;
140+
uint part;
141+
if (value >= 10000000)
142+
{
143+
if (value >= 100000000000000)
144+
{
145+
part = (uint)(value / 100000000000000);
146+
digits += 14;
147+
}
148+
else
149+
{
150+
part = (uint)(value / 10000000);
151+
digits += 7;
152+
}
153+
}
154+
else
155+
{
156+
part = (uint)value;
157+
}
158+
159+
if (part < 10)
160+
{
161+
// no-op
162+
}
163+
else if (part < 100)
164+
{
165+
digits += 1;
166+
}
167+
else if (part < 1000)
168+
{
169+
digits += 2;
170+
}
171+
else if (part < 10000)
172+
{
173+
digits += 3;
174+
}
175+
else if (part < 100000)
176+
{
177+
digits += 4;
178+
}
179+
else if (part < 1000000)
180+
{
181+
digits += 5;
182+
}
183+
else
184+
{
185+
Debug.Assert(part < 10000000);
186+
digits += 6;
187+
}
188+
189+
return digits;
190+
}
191+
121192
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122193
public static int CountFractionDigits(long n)
123194
{

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.D.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ private static bool TryFormatInt64D(long value, byte precision, Span<byte> buffe
4545
}
4646

4747
Unsafe.Add(ref utf8Bytes, idx++) = (byte)'9';
48-
idx += FormattingHelpers.WriteDigits(223372036854775808L, 18, ref utf8Bytes, idx);
48+
FormattingHelpers.WriteDigits(223372036854775808L, 18, ref utf8Bytes, idx);
4949

50-
bytesWritten = idx;
50+
bytesWritten = idx + 18;
5151
return true;
5252
}
5353

@@ -61,9 +61,9 @@ private static bool TryFormatInt64D(long value, byte precision, Span<byte> buffe
6161
Unsafe.Add(ref utf8Bytes, idx++) = (byte)'0';
6262
}
6363

64-
idx += FormattingHelpers.WriteDigits(value, digitCount, ref utf8Bytes, idx);
64+
FormattingHelpers.WriteDigits(value, digitCount, ref utf8Bytes, idx);
6565

66-
bytesWritten = idx;
66+
bytesWritten = digitCount + idx;
6767
return true;
6868
}
6969
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace System.Buffers.Text
6+
{
7+
/// <summary>
8+
/// Methods to format common data types as Utf8 strings.
9+
/// </summary>
10+
public static partial class Utf8Formatter
11+
{
12+
private static bool TryFormatInt64Default(long value, Span<byte> buffer, out int bytesWritten)
13+
{
14+
if ((ulong)value < 10)
15+
{
16+
if (buffer.Length == 0) goto FalseExit;
17+
buffer[0] = (byte)('0' + value);
18+
bytesWritten = 1;
19+
return true;
20+
}
21+
22+
if (value < 0)
23+
{
24+
value = -value;
25+
int digitCount = FormattingHelpers.CountDigits((ulong)value);
26+
if (digitCount >= buffer.Length) goto FalseExit;
27+
bytesWritten = digitCount + 1;
28+
buffer[0] = Utf8Constants.Minus;
29+
buffer = buffer.Slice(1, digitCount);
30+
}
31+
else
32+
{
33+
int digitCount = FormattingHelpers.CountDigits((ulong)value);
34+
if (digitCount > buffer.Length) goto FalseExit;
35+
bytesWritten = digitCount;
36+
buffer = buffer.Slice(0, digitCount);
37+
}
38+
39+
// WriteDigits does not do bounds checks
40+
FormattingHelpers.WriteDigits((ulong)value, buffer);
41+
return true;
42+
43+
FalseExit:
44+
bytesWritten = 0;
45+
return false;
46+
}
47+
}
48+
}

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ private static bool TryFormatInt64(long value, ulong mask, Span<byte> buffer, ou
1919
{
2020
if (format.IsDefault)
2121
{
22-
// Officially, the default is "G" but "G without a precision is equivalent to "D" and so that's why we're using "D" (eliminates an unnecessary HasPrecision check)
23-
format = 'D';
22+
return TryFormatInt64Default(value, buffer, out bytesWritten);
2423
}
2524

2625
switch (format.Symbol)

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.D.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ private static bool TryFormatUInt64D(ulong value, byte precision, Span<byte> buf
3636
}
3737

3838
ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference();
39-
bytesWritten += FormattingHelpers.WriteDigits(lastDigit, 1, ref utf8Bytes, bytesWritten);
39+
FormattingHelpers.WriteDigits(lastDigit, 1, ref utf8Bytes, bytesWritten);
40+
bytesWritten += 1;
4041
return true;
4142
}
4243
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace System.Buffers.Text
6+
{
7+
/// <summary>
8+
/// Methods to format common data types as Utf8 strings.
9+
/// </summary>
10+
public static partial class Utf8Formatter
11+
{
12+
private static bool TryFormatUInt64Default(ulong value, Span<byte> buffer, out int bytesWritten)
13+
{
14+
if (value < 10)
15+
{
16+
if (buffer.Length == 0) goto FalseExit;
17+
buffer[0] = (byte)('0' + value);
18+
bytesWritten = 1;
19+
return true;
20+
}
21+
22+
int digitCount = FormattingHelpers.CountDigits(value);
23+
if (digitCount > buffer.Length) goto FalseExit;
24+
bytesWritten = digitCount;
25+
// WriteDigits does not do bounds checks
26+
FormattingHelpers.WriteDigits(value, buffer.Slice(0, digitCount));
27+
return true;
28+
29+
FalseExit:
30+
bytesWritten = 0;
31+
return false;
32+
}
33+
}
34+
}

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.N.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ private static bool TryFormatUInt64N(ulong value, byte precision, Span<byte> buf
4747

4848
// Write the last group
4949
Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Separator;
50-
idx += FormattingHelpers.WriteDigits(lastGroup, 3, ref utf8Bytes, idx);
50+
FormattingHelpers.WriteDigits(lastGroup, 3, ref utf8Bytes, idx);
51+
idx += 3;
5152

5253
// Write out the trailing zeros
5354
if (precision > 0)

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ private static bool TryFormatUInt64(ulong value, Span<byte> buffer, out int byte
1919
{
2020
if (format.IsDefault)
2121
{
22-
// Officially, the default is "G" but "G without a precision is equivalent to "D" and so that's why we're using "D" (eliminates an unnecessary HasPrecision check)
23-
format = 'D';
22+
return TryFormatUInt64Default(value, buffer, out bytesWritten);
2423
}
2524

2625
switch (format.Symbol)

src/System.Memory/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.TimeSpan.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,27 @@ public static bool TryFormat(TimeSpan value, Span<byte> buffer, out int bytesWri
9191

9292
if (dayDigits > 0)
9393
{
94-
idx += FormattingHelpers.WriteDigits(days, dayDigits, ref utf8Bytes, idx);
94+
FormattingHelpers.WriteDigits(days, dayDigits, ref utf8Bytes, idx);
95+
idx += dayDigits;
9596
Unsafe.Add(ref utf8Bytes, idx++) = constant ? Utf8Constants.Period : Utf8Constants.Colon;
9697
}
9798

98-
idx += FormattingHelpers.WriteDigits(hours, hourDigits, ref utf8Bytes, idx);
99+
FormattingHelpers.WriteDigits(hours, hourDigits, ref utf8Bytes, idx);
100+
idx += hourDigits;
99101
Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Colon;
100102

101-
idx += FormattingHelpers.WriteDigits(minutes, 2, ref utf8Bytes, idx);
103+
FormattingHelpers.WriteDigits(minutes, 2, ref utf8Bytes, idx);
104+
idx += 2;
102105
Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Colon;
103106

104-
idx += FormattingHelpers.WriteDigits(seconds, 2, ref utf8Bytes, idx);
107+
FormattingHelpers.WriteDigits(seconds, 2, ref utf8Bytes, idx);
108+
idx += 2;
105109

106110
if (fractionDigits > 0)
107111
{
108112
Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Period;
109-
idx += FormattingHelpers.WriteFractionDigits(fraction, fractionDigits, ref utf8Bytes, idx);
113+
FormattingHelpers.WriteFractionDigits(fraction, fractionDigits, ref utf8Bytes, idx);
114+
idx += fractionDigits;
110115
}
111116

112117
return true;

0 commit comments

Comments
 (0)