Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize BigInteger.Parse #97589

Merged
merged 25 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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