Skip to content

Commit

Permalink
Optimize BigInteger.Parse (#97589)
Browse files Browse the repository at this point in the history
* Optimize NumberToBigInteger

* DisableParallelization

* Trailing zero

* Fix assertion

* Recursive parse

* Skip trailing zeros

* Shrink PowersOf1e9

* OmittedLength

* pre-calculate 1000000000^(1<<5)

* Move PowersOf1e9

* fix

* Fix large case

* public

* Use PowersOf1e9.MaxPartialDigits, PowersOf1e9.TenPowMaxPartial

* Remove NumberBufferToBigInteger

* intDigits

* Use BigInteger(ReadOnlySpan<uint> value, bool negative)

* base1E9

* Remove RecursiveLarge
  • Loading branch information
kzrnm authored Jul 3, 2024
1 parent 015b7ed commit 7a5fb4f
Show file tree
Hide file tree
Showing 7 changed files with 617 additions and 401 deletions.
759 changes: 459 additions & 300 deletions src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ internal BigInteger(int n, uint[]? rgu)
/// </summary>
/// <param name="value">The absolute value of the number</param>
/// <param name="negative">The bool indicating the sign of the value.</param>
private BigInteger(ReadOnlySpan<uint> value, bool negative)
internal BigInteger(ReadOnlySpan<uint> value, bool negative)
{
// Try to conserve space as much as possible by checking for wasted leading span entries
// sometimes the span has leading zeros from bit manipulation operations & and ^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<u
Add(left, bits, ref resultPtr, startIndex: i, initialCarry: carry);
}

private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
public static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
{
Debug.Assert(left.Length >= right.Length);

Expand Down Expand Up @@ -129,7 +129,7 @@ public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, S
Subtract(left, bits, ref resultPtr, startIndex: i, initialCarry: carry);
}

private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
public static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
{
Debug.Assert(left.Length >= right.Length);
// Assertion failing per https://github.com/dotnet/runtime/issues/97780
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static int Compare(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right)
return left[iv] < right[iv] ? -1 : 1;
}

private static int ActualLength(ReadOnlySpan<uint> value)
public static int ActualLength(ReadOnlySpan<uint> value)
{
// Since we're reusing memory here, the actual length
// of a given value may be less then the array's length
Expand Down
36 changes: 20 additions & 16 deletions src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,6 @@ public static void RunMultiply_TwoLargeBigIntegers()
}
}

[Fact]
public static void RunMultiply_TwoLargeBigIntegers_Threshold()
{
// Again, with lower threshold
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () =>
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, RunMultiply_TwoLargeBigIntegers)
);

// Again, with lower threshold
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () =>
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, () =>
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.StackAllocThreshold, 8, RunMultiply_TwoLargeBigIntegers)
)
);
}

[Fact]
public static void RunMultiply_TwoSmallBigIntegers()
{
Expand Down Expand Up @@ -293,4 +277,24 @@ private static string Print(byte[] bytes)
return MyBigIntImp.Print(bytes);
}
}

[Collection(nameof(DisableParallelization))]
public class multiplyTestThreshold
{
[Fact]
public static void RunMultiply_TwoLargeBigIntegers()
{
// Again, with lower threshold
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () =>
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, multiplyTest.RunMultiply_TwoLargeBigIntegers)
);

// Again, with lower threshold
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () =>
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, () =>
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.StackAllocThreshold, 8, multiplyTest.RunMultiply_TwoLargeBigIntegers)
)
);
}
}
}
214 changes: 133 additions & 81 deletions src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
using System.Diagnostics;
using System.Globalization;
using System.Tests;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;
using static System.Net.Mime.MediaTypeNames;

namespace System.Numerics.Tests
{
Expand Down Expand Up @@ -36,57 +38,51 @@ public static IEnumerable<object[]> Cultures
[OuterLoop]
public static void RunParseToStringTests(CultureInfo culture)
{
Test();
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);

void Test()
{
byte[] tempByteArray1 = new byte[0];
using (new ThreadCultureChange(culture))
byte[] tempByteArray1 = new byte[0];
using (new ThreadCultureChange(culture))
{
//default style
VerifyDefaultParse(s_random);

//single NumberStyles
VerifyNumberStyles(NumberStyles.None, s_random);
VerifyNumberStyles(NumberStyles.AllowLeadingWhite, s_random);
VerifyNumberStyles(NumberStyles.AllowTrailingWhite, s_random);
VerifyNumberStyles(NumberStyles.AllowLeadingSign, s_random);
VerifyNumberStyles(NumberStyles.AllowTrailingSign, s_random);
VerifyNumberStyles(NumberStyles.AllowParentheses, s_random);
VerifyNumberStyles(NumberStyles.AllowDecimalPoint, s_random);
VerifyNumberStyles(NumberStyles.AllowThousands, s_random);
VerifyNumberStyles(NumberStyles.AllowExponent, s_random);
VerifyNumberStyles(NumberStyles.AllowCurrencySymbol, s_random);
VerifyNumberStyles(NumberStyles.AllowHexSpecifier, s_random);
VerifyBinaryNumberStyles(NumberStyles.AllowBinarySpecifier, s_random);

//composite NumberStyles
VerifyNumberStyles(NumberStyles.Integer, s_random);
VerifyNumberStyles(NumberStyles.HexNumber, s_random);
VerifyBinaryNumberStyles(NumberStyles.BinaryNumber, s_random);
VerifyNumberStyles(NumberStyles.Number, s_random);
VerifyNumberStyles(NumberStyles.Float, s_random);
VerifyNumberStyles(NumberStyles.Currency, s_random);
VerifyNumberStyles(NumberStyles.Any, s_random);

//invalid number style
// ******InvalidNumberStyles
NumberStyles invalid = (NumberStyles)0x7c00;
AssertExtensions.Throws<ArgumentException>("style", () =>
{
//default style
VerifyDefaultParse(s_random);

//single NumberStyles
VerifyNumberStyles(NumberStyles.None, s_random);
VerifyNumberStyles(NumberStyles.AllowLeadingWhite, s_random);
VerifyNumberStyles(NumberStyles.AllowTrailingWhite, s_random);
VerifyNumberStyles(NumberStyles.AllowLeadingSign, s_random);
VerifyNumberStyles(NumberStyles.AllowTrailingSign, s_random);
VerifyNumberStyles(NumberStyles.AllowParentheses, s_random);
VerifyNumberStyles(NumberStyles.AllowDecimalPoint, s_random);
VerifyNumberStyles(NumberStyles.AllowThousands, s_random);
VerifyNumberStyles(NumberStyles.AllowExponent, s_random);
VerifyNumberStyles(NumberStyles.AllowCurrencySymbol, s_random);
VerifyNumberStyles(NumberStyles.AllowHexSpecifier, s_random);
VerifyBinaryNumberStyles(NumberStyles.AllowBinarySpecifier, s_random);

//composite NumberStyles
VerifyNumberStyles(NumberStyles.Integer, s_random);
VerifyNumberStyles(NumberStyles.HexNumber, s_random);
VerifyBinaryNumberStyles(NumberStyles.BinaryNumber, s_random);
VerifyNumberStyles(NumberStyles.Number, s_random);
VerifyNumberStyles(NumberStyles.Float, s_random);
VerifyNumberStyles(NumberStyles.Currency, s_random);
VerifyNumberStyles(NumberStyles.Any, s_random);

//invalid number style
// ******InvalidNumberStyles
NumberStyles invalid = (NumberStyles)0x7c00;
AssertExtensions.Throws<ArgumentException>("style", () =>
{
BigInteger.Parse("1", invalid).ToString("d");
});
AssertExtensions.Throws<ArgumentException>("style", () =>
{
BigInteger junk;
BigInteger.TryParse("1", invalid, null, out junk);
Assert.Equal("1", junk.ToString("d"));
});
BigInteger.Parse("1", invalid).ToString("d");
});
AssertExtensions.Throws<ArgumentException>("style", () =>
{
BigInteger junk;
BigInteger.TryParse("1", invalid, null, out junk);
Assert.Equal("1", junk.ToString("d"));
});

//FormatProvider tests
RunFormatProviderParseStrings();
}
//FormatProvider tests
RunFormatProviderParseStrings();
}
}

Expand All @@ -99,36 +95,23 @@ void Test()
[InlineData("1\03456789", 0, 1, "1")]
[InlineData("1\03456789", 0, 2, "1")]
[InlineData("123456789\0", 0, 10, "123456789")]
public void Parse_Subspan_Success(string input, int offset, int length, string expected)
public static void Parse_Subspan_Success(string input, int offset, int length, string expected)
{
Test();

BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);

void Test()
{
Eval(BigInteger.Parse(input.AsSpan(offset, length)), expected);
Assert.True(BigInteger.TryParse(input.AsSpan(offset, length), out BigInteger test));
Eval(test, expected);
}
Eval(BigInteger.Parse(input.AsSpan(offset, length)), expected);
Assert.True(BigInteger.TryParse(input.AsSpan(offset, length), out BigInteger test));
Eval(test, expected);
}

[Fact]
public void Parse_EmptySubspan_Fails()
public static void Parse_EmptySubspan_Fails()
{
Test();
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);

void Test()
{
BigInteger result;
BigInteger result;

Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out result));
Assert.Equal(0, result);
Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out result));
Assert.Equal(0, result);

Assert.False(BigInteger.TryParse([], out result));
Assert.Equal(0, result);
}
Assert.False(BigInteger.TryParse([], out result));
Assert.Equal(0, result);
}

[Fact]
Expand All @@ -148,7 +131,7 @@ public void Parse_Hex32Bits()

Assert.True(BigInteger.TryParse("0F0000001", NumberStyles.HexNumber, null, out result));
Assert.Equal(0xF0000001u, result);

Assert.True(BigInteger.TryParse("F00000001", NumberStyles.HexNumber, null, out result));
Assert.Equal(-0xFFFFFFFFL, result);

Expand Down Expand Up @@ -210,16 +193,10 @@ public static IEnumerable<object[]> RegressionIssueRuntime94610_TestData()

[Theory]
[MemberData(nameof(RegressionIssueRuntime94610_TestData))]
public void RegressionIssueRuntime94610(string text)
public static void RegressionIssueRuntime94610(string text)
{
// Regression test for: https://github.com/dotnet/runtime/issues/94610
Test();
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);

void Test()
{
VerifyParseToString(text, NumberStyles.Integer, true);
}
VerifyParseToString(text, NumberStyles.Integer, true);
}

private static void RunFormatProviderParseStrings()
Expand Down Expand Up @@ -268,6 +245,19 @@ private static void VerifyDefaultParse(Random random)
VerifyParseToString(GetDigitSequence(100, 1000, random));
}

// Trailing Zero - Small
VerifyParseToString("99000000000");
for (int i = 0; i < s_samples; i++)
{
VerifyParseToString(GetDigitSequence(1, 10, random) + new string('0', random.Next(10, 50)));
}

// Trailing Zero - Large
for (int i = 0; i < s_samples; i++)
{
VerifyParseToString(GetDigitSequence(10, 100, random) + new string('0', random.Next(100, 1000)));
}

// Leading White
for (int i = 0; i < s_samples; i++)
{
Expand Down Expand Up @@ -531,6 +521,13 @@ private static void VerifyNumberStyles(NumberStyles ns, Random random)
VerifyParseToString(GetDigitSequence(100, 1000, random), ns, true);
}

// Trailing Zero
VerifyParseToString("99000000000", ns, true);
for (int i = 0; i < s_samples; i++)
{
VerifyParseToString(GetDigitSequence(1, 10, random) + "1000000000", ns, true);
}

// Leading White
for (int i = 0; i < s_samples; i++)
{
Expand Down Expand Up @@ -1169,4 +1166,59 @@ private static void Eval(BigInteger x, string expected)
Assert.Equal(expected, actual);
}
}

[Collection(nameof(DisableParallelization))]
public class parseTestThreshold
{
public static IEnumerable<object[]> Cultures => parseTest.Cultures;
[Theory]
[MemberData(nameof(Cultures))]
[OuterLoop]
public static void RunParseToStringTests(CultureInfo culture)
{
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () =>
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () =>
{
parseTest.RunParseToStringTests(culture);
}));
}

[Theory]
[InlineData("123456789", 0, 9, "123456789")]
[InlineData("123456789", 0, 1, "1")]
[InlineData("123456789", 1, 3, "234")]
[InlineData("123456789", 8, 1, "9")]
[InlineData("123456789abc", 8, 1, "9")]
[InlineData("1\03456789", 0, 1, "1")]
[InlineData("1\03456789", 0, 2, "1")]
[InlineData("123456789\0", 0, 10, "123456789")]
public static void Parse_Subspan_Success(string input, int offset, int length, string expected)
{
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () =>
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () =>
{
parseTest.Parse_Subspan_Success(input, offset, length, expected);
}));
}

[Fact]
public static void Parse_EmptySubspan_Fails()
{
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () =>
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, parseTest.Parse_EmptySubspan_Fails));
}

public static IEnumerable<object[]> RegressionIssueRuntime94610_TestData() => parseTest.RegressionIssueRuntime94610_TestData();

[Theory]
[MemberData(nameof(RegressionIssueRuntime94610_TestData))]
public static void RegressionIssueRuntime94610(string text)
{
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () =>
BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () =>
{
parseTest.RegressionIssueRuntime94610(text);
}));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<Compile Include="ComplexTests.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs" Link="Common\TestUtilities\System\DisableParallelization.cs" />
<Compile Include="$(CommonTestPath)System\GenericMathHelpers.cs" Link="Common\System\GenericMathHelpers.cs" />
<Compile Include="$(CommonTestPath)System\Diagnostics\DebuggerAttributes.cs" Link="Common\System\Diagnostics\DebuggerAttributes.cs" />
<Compile Include="BigIntegerTests.GenericMath.cs" />
Expand Down

0 comments on commit 7a5fb4f

Please sign in to comment.