From b41c8cc8f60e0c05c79f82ccf9aa82a21b41a280 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 4 Apr 2018 22:19:52 -0400 Subject: [PATCH] Use FormattingHelpers.Count{Hex}Digits in {u}int/long.ToString/TryFormat Currently we create a temporary buffer on the stack, format into it, and then copy from that stack buffer into either the target span (for TryFormat) or into a new string (for ToString. Following the approach as (and sharing the same code from) Utf8Formatter, where it first counts the number of digits in the output in order to determine an exact length, this commit changes the implementation to skip the temporary buffer and just format directly into the destination span or string. This results in a very measurable performance boost. --- .../shared/System/Number.Formatting.cs | 335 ++++++++++-------- 1 file changed, 196 insertions(+), 139 deletions(-) diff --git a/src/mscorlib/shared/System/Number.Formatting.cs b/src/mscorlib/shared/System/Number.Formatting.cs index 24d5db1da9ec..74395ab2f58a 100644 --- a/src/mscorlib/shared/System/Number.Formatting.cs +++ b/src/mscorlib/shared/System/Number.Formatting.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers.Text; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; @@ -246,9 +247,7 @@ internal static partial class Number private const int DoublePrecision = 15; private const int ScaleNAN = unchecked((int)0x80000000); private const int ScaleINF = 0x7FFFFFFF; - private const int MaxUInt32HexDigits = 8; private const int MaxUInt32DecDigits = 10; - private const int MaxUInt64DecDigits = 20; private const int CharStackBufferSize = 32; private const string PosNumberFormat = "#"; @@ -972,18 +971,20 @@ private static unsafe string NegativeInt32ToDecStr(int value, int digits, string if (digits < 1) digits = 1; - int bufferLength = Math.Max(digits, MaxUInt32DecDigits) + sNegative.Length; - int index = bufferLength; - - char* buffer = stackalloc char[bufferLength]; - char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits); - for (int i = sNegative.Length - 1; i >= 0; i--) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length; + string result = string.FastAllocateString(bufferLength); + fixed (char* buffer = result) { - *(--p) = sNegative[i]; - } + char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits); + Debug.Assert(p == buffer + sNegative.Length); - Debug.Assert(buffer + bufferLength - p >= 0 && buffer <= p); - return new string(p, 0, (int)(buffer + bufferLength - p)); + for (int i = sNegative.Length - 1; i >= 0; i--) + { + *(--p) = sNegative[i]; + } + Debug.Assert(p == buffer); + } + return result; } private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span destination, out int charsWritten) @@ -993,18 +994,26 @@ private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, strin if (digits < 1) digits = 1; - int bufferLength = Math.Max(digits, MaxUInt32DecDigits) + sNegative.Length; - int index = bufferLength; - - char* buffer = stackalloc char[bufferLength]; - char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits); - for (int i = sNegative.Length - 1; i >= 0; i--) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length; + if (bufferLength > destination.Length) { - *(--p) = sNegative[i]; + charsWritten = 0; + return false; } - Debug.Assert(buffer + bufferLength - p >= 0 && buffer <= p); - return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten); + charsWritten = bufferLength; + fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + { + char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits); + Debug.Assert(p == buffer + sNegative.Length); + + for (int i = sNegative.Length - 1; i >= 0; i--) + { + *(--p) = sNegative[i]; + } + Debug.Assert(p == buffer); + } + return true; } private static unsafe string Int32ToHexStr(int value, char hexBase, int digits) @@ -1012,11 +1021,14 @@ private static unsafe string Int32ToHexStr(int value, char hexBase, int digits) if (digits < 1) digits = 1; - int bufferLength = Math.Max(digits, MaxUInt32HexDigits); - char* buffer = stackalloc char[bufferLength]; - - char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits); - return new string(p, 0, (int)(buffer + bufferLength - p)); + int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value)); + string result = string.FastAllocateString(bufferLength); + fixed (char* buffer = result) + { + char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits); + Debug.Assert(p == buffer); + } + return result; } private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span destination, out int charsWritten) @@ -1024,11 +1036,20 @@ private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, if (digits < 1) digits = 1; - int bufferLength = Math.Max(digits, MaxUInt32HexDigits); - char* buffer = stackalloc char[bufferLength]; + int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value)); + if (bufferLength > destination.Length) + { + charsWritten = 0; + return false; + } - char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits); - return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten); + charsWritten = bufferLength; + fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + { + char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits); + Debug.Assert(p == buffer); + } + return true; } private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits) @@ -1073,56 +1094,62 @@ private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) private static unsafe string UInt32ToDecStr(uint value, int digits) { - if (digits <= 1) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)); + string result = string.FastAllocateString(bufferLength); + fixed (char* buffer = result) { - char* buffer = stackalloc char[MaxUInt32DecDigits]; - - char* start = buffer + MaxUInt32DecDigits; - char* p = start; - do + char* p = buffer + bufferLength; + if (digits <= 1) + { + do + { + // TODO https://github.com/dotnet/coreclr/issues/3439 + uint div = value / 10; + *(--p) = (char)('0' + value - (div * 10)); + value = div; + } + while (value != 0); + } + else { - // TODO https://github.com/dotnet/coreclr/issues/3439 - uint div = value / 10; - *(--p) = (char)('0' + value - (div * 10)); - value = div; + p = UInt32ToDecChars(p, value, digits); } - while (value != 0); - - return new string(p, 0, (int)(start - p)); - } - else - { - int bufferSize = Math.Max(digits, MaxUInt32DecDigits); - char* buffer = stackalloc char[bufferSize]; - char* p = UInt32ToDecChars(buffer + bufferSize, value, digits); - return new string(p, 0, (int)(buffer + bufferSize - p)); + Debug.Assert(p == buffer); } + return result; } private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span destination, out int charsWritten) { - if (digits <= 1) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)); + if (bufferLength > destination.Length) { - char* buffer = stackalloc char[MaxUInt32DecDigits]; - char* start = buffer + MaxUInt32DecDigits; - char* p = start; - do - { - // TODO https://github.com/dotnet/coreclr/issues/3439 - uint div = value / 10; - *(--p) = (char)('0' + value - (div * 10)); - value = div; - } - while (value != 0); - return TryCopyTo(p, (int)(start - p), destination, out charsWritten); + charsWritten = 0; + return false; } - else + + charsWritten = bufferLength; + fixed (char* buffer = &MemoryMarshal.GetReference(destination)) { - int bufferSize = Math.Max(digits, MaxUInt32DecDigits); - char* buffer = stackalloc char[bufferSize]; - char* p = UInt32ToDecChars(buffer + bufferSize, value, digits); - return TryCopyTo(p, (int)(buffer + bufferSize - p), destination, out charsWritten); + char* p = buffer + bufferLength; + if (digits <= 1) + { + do + { + // TODO https://github.com/dotnet/coreclr/issues/3439 + uint div = value / 10; + *(--p) = (char)('0' + value - (div * 10)); + value = div; + } + while (value != 0); + } + else + { + p = UInt32ToDecChars(p, value, digits); + } + Debug.Assert(p == buffer); } + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1176,24 +1203,26 @@ private static unsafe string NegativeInt64ToDecStr(long input, int digits, strin ulong value = (ulong)(-input); - int bufferLength = Math.Max(digits, MaxUInt64DecDigits) + sNegative.Length; - int index = bufferLength; - - char* buffer = stackalloc char[bufferLength]; - char* p = buffer + bufferLength; - while (High32(value) != 0) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)) + sNegative.Length; + string result = string.FastAllocateString(bufferLength); + fixed (char* buffer = result) { - p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); - digits -= 9; - } - p = UInt32ToDecChars(p, Low32(value), digits); + char* p = buffer + bufferLength; + while (High32(value) != 0) + { + p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); + digits -= 9; + } + p = UInt32ToDecChars(p, Low32(value), digits); + Debug.Assert(p == buffer + sNegative.Length); - for (int i = sNegative.Length - 1; i >= 0; i--) - { - *(--p) = sNegative[i]; + for (int i = sNegative.Length - 1; i >= 0; i--) + { + *(--p) = sNegative[i]; + } + Debug.Assert(p == buffer); } - - return new string(p, 0, (int)(buffer + bufferLength - p)); + return result; } private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span destination, out int charsWritten) @@ -1207,64 +1236,80 @@ private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, stri ulong value = (ulong)(-input); - int bufferLength = Math.Max(digits, MaxUInt64DecDigits) + sNegative.Length; - int index = bufferLength; - - char* buffer = stackalloc char[bufferLength]; - char* p = buffer + bufferLength; - while (High32(value) != 0) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-input))) + sNegative.Length; + if (bufferLength > destination.Length) { - p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); - digits -= 9; + charsWritten = 0; + return false; } - p = UInt32ToDecChars(p, Low32(value), digits); - for (int i = sNegative.Length - 1; i >= 0; i--) + charsWritten = bufferLength; + fixed (char* buffer = &MemoryMarshal.GetReference(destination)) { - *(--p) = sNegative[i]; - } + char* p = buffer + bufferLength; + while (High32(value) != 0) + { + p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); + digits -= 9; + } + p = UInt32ToDecChars(p, Low32(value), digits); + Debug.Assert(p == buffer + sNegative.Length); - return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten); + for (int i = sNegative.Length - 1; i >= 0; i--) + { + *(--p) = sNegative[i]; + } + Debug.Assert(p == buffer); + } + return true; } private static unsafe string Int64ToHexStr(long value, char hexBase, int digits) { - int bufferLength = Math.Max(digits, MaxUInt32HexDigits * 2); - char* buffer = stackalloc char[bufferLength]; - int index = bufferLength; - - char* p; - if (High32((ulong)value) != 0) + int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value)); + string result = string.FastAllocateString(bufferLength); + fixed (char* buffer = result) { - p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, 8); - p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8); - } - else - { - p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, Math.Max(digits, 1)); + char* p = buffer + bufferLength; + if (High32((ulong)value) != 0) + { + p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8); + p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8); + } + else + { + p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1)); + } + Debug.Assert(p == buffer); } - - return new string(p, 0, (int)(buffer + bufferLength - p)); + return result; } private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span destination, out int charsWritten) { - int bufferLength = Math.Max(digits, MaxUInt32HexDigits * 2); - char* buffer = stackalloc char[bufferLength]; - int index = bufferLength; - - char* p; - if (High32((ulong)value) != 0) + int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value)); + if (bufferLength > destination.Length) { - p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, 8); - p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8); + charsWritten = 0; + return false; } - else + + charsWritten = bufferLength; + fixed (char* buffer = &MemoryMarshal.GetReference(destination)) { - p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, Math.Max(digits, 1)); + char* p = buffer + bufferLength; + if (High32((ulong)value) != 0) + { + p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8); + p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8); + } + else + { + p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1)); + } + Debug.Assert(p == buffer); } - - return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten); + return true; } private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number) @@ -1293,17 +1338,20 @@ private static unsafe string UInt64ToDecStr(ulong value, int digits) if (digits < 1) digits = 1; - int bufferSize = Math.Max(digits, MaxUInt64DecDigits); - char* buffer = stackalloc char[bufferSize]; - char* p = buffer + bufferSize; - while (High32(value) != 0) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)); + string result = string.FastAllocateString(bufferLength); + fixed (char* buffer = result) { - p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); - digits -= 9; + char* p = buffer + bufferLength; + while (High32(value) != 0) + { + p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); + digits -= 9; + } + p = UInt32ToDecChars(p, Low32(value), digits); + Debug.Assert(p == buffer); } - p = UInt32ToDecChars(p, Low32(value), digits); - - return new string(p, 0, (int)(buffer + bufferSize - p)); + return result; } private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span destination, out int charsWritten) @@ -1311,17 +1359,26 @@ private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span if (digits < 1) digits = 1; - int bufferSize = Math.Max(digits, MaxUInt64DecDigits); - char* buffer = stackalloc char[bufferSize]; - char* p = buffer + bufferSize; - while (High32(value) != 0) + int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)); + if (bufferLength > destination.Length) { - p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); - digits -= 9; + charsWritten = 0; + return false; } - p = UInt32ToDecChars(p, Low32(value), digits); - return TryCopyTo(p, (int)(buffer + bufferSize - p), destination, out charsWritten); + charsWritten = bufferLength; + fixed (char* buffer = &MemoryMarshal.GetReference(destination)) + { + char* p = buffer + bufferLength; + while (High32(value) != 0) + { + p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9); + digits -= 9; + } + p = UInt32ToDecChars(p, Low32(value), digits); + Debug.Assert(p == buffer); + } + return true; } internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out int digits)