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

Spannified internals of BigInteger #35565

Merged
merged 99 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
51260fd
Unsafe pointers replaced with managed pointers
sakno Apr 27, 2020
27cd3b6
Duplicate code replaced with static local
sakno Apr 28, 2020
f6d1f89
Spannified internals
sakno Apr 28, 2020
d8af4fd
Spannified compare
sakno Apr 28, 2020
ca1cf5c
Spannified div/rem operations
sakno Apr 28, 2020
d89cdea
Minus two array allocations for each bitwise operation
sakno Apr 28, 2020
7a6a0d8
Removed redundant locals
sakno Apr 29, 2020
eb64ad1
Sync with upstream
sakno Apr 29, 2020
ba2a01e
Code review feedback (code style)
sakno Apr 29, 2020
49317c5
Code review feedback
sakno Apr 29, 2020
fa47aea
LeadingZeroes replaced with BitOperations
sakno Apr 29, 2020
ea7c29b
stackalloc in Divide without remainder
sakno Apr 29, 2020
a1e5006
Reduced memory allocation for bitwise operations
sakno Apr 29, 2020
f19ff53
Managed pointer replaced with Span
sakno Apr 29, 2020
6e8705f
Removed namespace imports
sakno Apr 29, 2020
34893a3
Removed delegate type
sakno Apr 30, 2020
5992406
Reduced memory allocation in the end of each bitwise operation
sakno Apr 30, 2020
2b7b7ba
Code review feedback
sakno Apr 30, 2020
03313c1
Removed redundant array allocation
sakno Apr 30, 2020
92618a4
Trivial division moved to stack alloc
sakno Apr 30, 2020
65f2701
Reduced memory allocation for divide operator
sakno Apr 30, 2020
b5a77b4
Reduced memory allocation for shift operations
sakno Apr 30, 2020
ea35011
Array pooling for Square operation
sakno Apr 30, 2020
9da2cb3
Fixed bound check for bitwise operations
sakno Apr 30, 2020
ed6d159
Multiply now uses array pooling
sakno Apr 30, 2020
e53dc14
Removed namespace import
sakno Apr 30, 2020
6d84785
Reduced memory allocation for Add
sakno Apr 30, 2020
a4482e0
Removed swap of arguments
sakno Apr 30, 2020
b0752ea
Reduced memory allocations for multiply operator
sakno Apr 30, 2020
8c9cfdc
Reduced memory allocations for modulo operator
sakno Apr 30, 2020
15b4eef
Temporarily spannify ActualLength method
sakno Apr 30, 2020
fb263bf
Spannify FastReducer
sakno Apr 30, 2020
f71442d
Spannify initial parts of GCD alg
sakno May 1, 2020
156b7ac
Simple GCD test for debugging
sakno May 1, 2020
036c535
Simple test for debugging
sakno May 1, 2020
7fa1438
Removed debug code
sakno May 1, 2020
a4c4891
Spannified GCD algorithm
sakno May 1, 2020
900ac48
Removed arg passing by ref
sakno May 1, 2020
3a5a117
Complete migration of gcd to span
sakno May 1, 2020
97ee31b
Removed memory allocation for trivial case of GCD
sakno May 1, 2020
4e6a403
Reorganize utility code
sakno May 1, 2020
dd69148
Removed redundant parameter
sakno May 1, 2020
1f84dc7
Reduced memory alloc for trivial case of Pow alg
sakno May 2, 2020
875cd16
Use span for trivial modulus pow case
sakno May 2, 2020
19086a6
Use span for trivial cases of pow modulus
sakno May 2, 2020
64597dc
Fixed buffer cleanup
sakno May 2, 2020
f86c616
Reduced memory allocation of ModPow operations
sakno May 2, 2020
7509c59
BitsBuffer replaced with span
sakno May 2, 2020
61e028b
Reorganize utility methods
sakno May 2, 2020
4ff0bd5
Reduced visibility scope
sakno May 2, 2020
9e2242e
Removed temporary code for bits cleanup
sakno May 2, 2020
98b7fa8
Fill leading bits with zeroes
sakno May 2, 2020
f5a5b9e
Unify order of parameters
sakno May 2, 2020
f713703
Unify order of parameters
sakno May 2, 2020
0f1f935
Unify order of parameters
sakno May 2, 2020
c9ba177
Reuse the same local var
sakno May 2, 2020
1b12fde
Sync with master
sakno May 2, 2020
e1afe77
Removed whtespace
sakno May 2, 2020
f085cc4
Code review feedback
sakno May 3, 2020
30132f0
Avoid misleading with Span.CopyTo
sakno May 3, 2020
0344cb2
Clarify purpose of slicing
sakno May 3, 2020
4c6dd0b
Replace mentioning of uint[] in comments
sakno May 3, 2020
35bd53c
Fixed code formatting issues
sakno May 3, 2020
b7b3460
Merge with master
sakno Aug 30, 2020
9f498b0
Share StackAllocThreshold const
sakno Aug 30, 2020
7bc63c8
Ignore const threshold values in Release config
sakno Aug 30, 2020
dd7bdef
Literal field cannot be discovered with TypeInfo.GetDeclaredField
sakno Aug 30, 2020
fe53099
Removed invalid assertion
sakno Aug 30, 2020
55d949f
Removed invalid assertion
sakno Aug 30, 2020
61d470d
Removed invalid assertion
sakno Aug 30, 2020
8a7ba77
Sync with upstream
sakno Dec 15, 2020
808c12c
Code review feedback
sakno Dec 15, 2020
7db731c
Fixed header
sakno Dec 15, 2020
8762259
Sync with upstream
sakno Jan 16, 2021
24d2ff4
Fixed overflow of result buffer
sakno Jan 16, 2021
60777dd
Reduced ctor visibility
sakno Jan 16, 2021
fa5eb5e
Eliminated bounds check
sakno Jan 18, 2021
ec1285d
Returned optimization using Math.DivRem
sakno Jan 20, 2021
f01d089
Added braces to be consistent with other branches
sakno Jan 20, 2021
484d6ff
Fixed location of the comment
sakno Jan 20, 2021
e1161f0
Combine slice and conversion as the single line
sakno Jan 20, 2021
9855da5
Combine slice and conversion as the single line
sakno Jan 20, 2021
9a22a4e
Sync with upstream
sakno May 10, 2021
eeb2cdd
Merge with parsing optimizations
sakno May 10, 2021
1b30bb0
Reduced checks
sakno May 10, 2021
fb514a1
Removed redundant global const
sakno May 10, 2021
b4ac5e5
Replaced magic consts with their named equivalents
sakno May 10, 2021
932eef3
Removed useless asserts
sakno May 12, 2021
fd0124a
Merge branch 'main' into issue-22609
sakno Aug 5, 2021
a4f3f22
Manual merge with #53984 PR
sakno Aug 5, 2021
2a94300
Review feedback (use const for stackalloc)
sakno Aug 5, 2021
82187cb
Merge branch 'main' and #57297
sakno Aug 17, 2021
1a9cf2b
Sync with upstream
sakno Aug 24, 2021
4971224
Review feedback
sakno Aug 24, 2021
020e749
Unsafe code replaced with span indexer where possible
sakno Aug 24, 2021
68306d1
Removed unused local
sakno Aug 26, 2021
65485fa
Sync with upstream
sakno Oct 7, 2021
fe39909
Sync with upstream
sakno Oct 8, 2021
602574f
Review feedback
sakno Oct 8, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="System\Numerics\BigIntegerCalculator.AddSub.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.BitsBuffer.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.DivRem.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.FastReducer.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.GcdInv.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.PowMod.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.SquMul.cs" />
<Compile Include="System\Numerics\BigIntegerCalculator.Utils.cs" />
<Compile Include="System\Numerics\BigInteger.cs" />
<Compile Include="System\Numerics\BigNumber.cs" />
<Compile Include="System\Numerics\NumericsHelpers.cs" />
Expand All @@ -30,5 +30,6 @@
<Reference Include="System.Memory" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
</ItemGroup>
</Project>
862 changes: 561 additions & 301 deletions src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,275 +2,193 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Security;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Numerics
{
internal static partial class BigIntegerCalculator
{
public static uint[] Add(uint[] left, uint right)
public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits)
{
Debug.Assert(left != null);
Debug.Assert(left.Length >= 1);
Debug.Assert(bits.Length == left.Length + 1);

// Executes the addition for one big and one 32-bit integer.
// Thus, we've similar code than below, but there is no loop for
// processing the 32-bit integer, since it's a single element.

uint[] bits = new uint[left.Length + 1];
long carry = right;

long digit = (long)left[0] + right;
bits[0] = unchecked((uint)digit);
long carry = digit >> 32;

for (int i = 1; i < left.Length; i++)
for (int i = 0; i < left.Length; i++)
{
digit = left[i] + carry;
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
carry = digit >> 32;
}
bits[left.Length] = (uint)carry;

return bits;
bits[left.Length] = (uint)carry;
}

public static unsafe uint[] Add(uint[] left, uint[] right)
public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
{
Debug.Assert(left != null);
Debug.Assert(right != null);
Debug.Assert(left.Length >= 0);
Debug.Assert(right.Length >= 0);
Debug.Assert(left.Length >= right.Length);
Debug.Assert(bits.Length == left.Length + 1);

// Switching to unsafe pointers helps sparing
// some nasty index calculations...

uint[] bits = new uint[left.Length + 1];

fixed (uint* l = left, r = right, b = &bits[0])
{
Add(l, left.Length,
r, right.Length,
b, bits.Length);
}

return bits;
}
int i = 0;
long carry = 0L;

private static unsafe void Add(uint* left, int leftLength,
uint* right, int rightLength,
uint* bits, int bitsLength)
{
Debug.Assert(leftLength >= 0);
Debug.Assert(rightLength >= 0);
Debug.Assert(leftLength >= rightLength);
Debug.Assert(bitsLength == leftLength + 1);
// Switching to managed references helps eliminating
// index bounds check...
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
ref uint resultPtr = ref MemoryMarshal.GetReference(bits);

// Executes the "grammar-school" algorithm for computing z = a + b.
// While calculating z_i = a_i + b_i we take care of overflow:
// Since a_i + b_i + c <= 2(2^32 - 1) + 1 = 2^33 - 1, our carry c
// has always the value 1 or 0; hence, we're safe here.

int i = 0;
long carry = 0L;

for (; i < rightLength; i++)
for ( ; i < right.Length; i++)
{
long digit = (left[i] + carry) + right[i];
bits[i] = unchecked((uint)digit);
long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
for (; i < leftLength; i++)
for ( ; i < left.Length; i++)
{
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
bits[i] = (uint)carry;
Unsafe.Add(ref resultPtr, i) = (uint)carry;
}

private static unsafe void AddSelf(uint* left, int leftLength,
uint* right, int rightLength)
private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
{
Debug.Assert(leftLength >= 0);
Debug.Assert(rightLength >= 0);
Debug.Assert(leftLength >= rightLength);
Debug.Assert(left.Length >= 0);
Debug.Assert(right.Length >= 0);
Debug.Assert(left.Length >= right.Length);

int i = 0;
long carry = 0L;

// Switching to managed references helps eliminating
// index bounds check...
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
ref uint rightPtr = ref MemoryMarshal.GetReference(right);

// Executes the "grammar-school" algorithm for computing z = a + b.
// Same as above, but we're writing the result directly to a and
// stop execution, if we're out of b and c is already 0.

int i = 0;
long carry = 0L;

for (; i < rightLength; i++)
for ( ; i < right.Length; i++)
{
long digit = (left[i] + carry) + right[i];
left[i] = unchecked((uint)digit);
long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i];
Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
for (; carry != 0 && i < leftLength; i++)
for ( ; carry != 0 && i < left.Length; i++)
{
long digit = left[i] + carry;
left[i] = (uint)digit;
long digit = Unsafe.Add(ref leftPtr, i) + carry;
Unsafe.Add(ref leftPtr, i) = (uint)digit;
carry = digit >> 32;
}

Debug.Assert(carry == 0);
}

public static uint[] Subtract(uint[] left, uint right)
public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits)
{
Debug.Assert(left != null);
Debug.Assert(left.Length >= 1);
Debug.Assert(left[0] >= right || left.Length >= 2);
Debug.Assert(bits.Length == left.Length);

// Executes the subtraction for one big and one 32-bit integer.
// Thus, we've similar code than below, but there is no loop for
// processing the 32-bit integer, since it's a single element.

uint[] bits = new uint[left.Length];
long carry = -right;

long digit = (long)left[0] - right;
bits[0] = unchecked((uint)digit);
long carry = digit >> 32;

for (int i = 1; i < left.Length; i++)
for (int i = 0; i < left.Length; i++)
{
digit = left[i] + carry;
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
carry = digit >> 32;
}

return bits;
}

public static unsafe uint[] Subtract(uint[] left, uint[] right)
public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
{
Debug.Assert(left != null);
Debug.Assert(right != null);
Debug.Assert(left.Length >= 0);
sakno marked this conversation as resolved.
Show resolved Hide resolved
Debug.Assert(right.Length >= 0);
Debug.Assert(left.Length >= right.Length);
Debug.Assert(Compare(left, right) >= 0);
Debug.Assert(bits.Length == left.Length);

// Switching to unsafe pointers helps sparing
// some nasty index calculations...

uint[] bits = new uint[left.Length];

fixed (uint* l = left, r = right, b = bits)
{
Subtract(l, left.Length,
r, right.Length,
b, bits.Length);
}

return bits;
}
int i = 0;
long carry = 0L;

private static unsafe void Subtract(uint* left, int leftLength,
uint* right, int rightLength,
uint* bits, int bitsLength)
{
Debug.Assert(leftLength >= 0);
Debug.Assert(rightLength >= 0);
Debug.Assert(leftLength >= rightLength);
Debug.Assert(Compare(left, leftLength, right, rightLength) >= 0);
Debug.Assert(bitsLength == leftLength);
// Switching to managed references helps eliminating
// index bounds check...
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
ref uint resultPtr = ref MemoryMarshal.GetReference(bits);

// Executes the "grammar-school" algorithm for computing z = a - b.
// While calculating z_i = a_i - b_i we take care of overflow:
// Since a_i - b_i doesn't need any additional bit, our carry c
// has always the value -1 or 0; hence, we're safe here.

int i = 0;
long carry = 0L;

for (; i < rightLength; i++)
for ( ; i < right.Length; i++)
{
long digit = (left[i] + carry) - right[i];
bits[i] = unchecked((uint)digit);
long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
for (; i < leftLength; i++)
for ( ; i < left.Length; i++)
{
long digit = left[i] + carry;
bits[i] = (uint)digit;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
}

Debug.Assert(carry == 0);
}

private static unsafe void SubtractSelf(uint* left, int leftLength,
uint* right, int rightLength)
private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
{
Debug.Assert(leftLength >= 0);
Debug.Assert(rightLength >= 0);
Debug.Assert(leftLength >= rightLength);
Debug.Assert(Compare(left, leftLength, right, rightLength) >= 0);
Debug.Assert(left.Length >= 0);
Debug.Assert(right.Length >= 0);
Debug.Assert(left.Length >= right.Length);
Debug.Assert(Compare(left, right) >= 0);

int i = 0;
long carry = 0L;

// Switching to managed references helps eliminating
// index bounds check...
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
ref uint rightPtr = ref MemoryMarshal.GetReference(right);

// Executes the "grammar-school" algorithm for computing z = a - b.
// Same as above, but we're writing the result directly to a and
// stop execution, if we're out of b and c is already 0.

int i = 0;
long carry = 0L;

for (; i < rightLength; i++)
for (; i < right.Length; i++)
{
long digit = (left[i] + carry) - right[i];
left[i] = unchecked((uint)digit);
long digit = (Unsafe.Add(ref leftPtr, i) + carry) - Unsafe.Add(ref rightPtr, i);
Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
for (; carry != 0 && i < leftLength; i++)
for (; carry != 0 && i < left.Length; i++)
{
long digit = left[i] + carry;
left[i] = (uint)digit;
long digit = Unsafe.Add(ref leftPtr, i) + carry;
Unsafe.Add(ref leftPtr, i) = (uint)digit;
carry = digit >> 32;
}

Debug.Assert(carry == 0);
}

public static int Compare(uint[] left, uint[] right)
{
Debug.Assert(left != null);
Debug.Assert(right != null);

if (left.Length < right.Length)
return -1;
if (left.Length > right.Length)
return 1;

for (int i = left.Length - 1; i >= 0; i--)
{
if (left[i] < right[i])
return -1;
if (left[i] > right[i])
return 1;
}

return 0;
}

private static unsafe int Compare(uint* left, int leftLength,
uint* right, int rightLength)
{
Debug.Assert(leftLength >= 0);
Debug.Assert(rightLength >= 0);

if (leftLength < rightLength)
return -1;
if (leftLength > rightLength)
return 1;

for (int i = leftLength - 1; i >= 0; i--)
{
if (left[i] < right[i])
return -1;
if (left[i] > right[i])
return 1;
}

return 0;
}
}
}
Loading