Skip to content

Commit

Permalink
Spannified internals of BigInteger (#35565)
Browse files Browse the repository at this point in the history
* Unsafe pointers replaced with managed pointers

* Duplicate code replaced with static local

* Spannified internals

* Spannified compare

* Spannified div/rem operations

* Minus two array allocations for each bitwise operation

* Removed redundant locals

* Code review feedback (code style)

* Code review feedback

* LeadingZeroes replaced with BitOperations

* stackalloc in Divide without remainder

* Reduced memory allocation for bitwise operations

* Managed pointer replaced with Span

* Removed namespace imports

* Removed delegate type

* Reduced memory allocation in the end of each bitwise operation

* Code review feedback

* Removed redundant array allocation

* Trivial division moved to stack alloc

* Reduced memory allocation for divide operator

* Reduced memory allocation for shift operations

* Array pooling for Square operation

* Fixed bound check for bitwise operations

* Multiply now uses array pooling

* Removed namespace import

* Reduced memory allocation for Add

* Removed swap of arguments

* Reduced memory allocations for multiply operator

* Reduced memory allocations for modulo operator

* Temporarily spannify ActualLength method

* Spannify FastReducer

* Spannify initial parts of GCD alg

* Simple GCD test for debugging

* Simple test for debugging

* Removed debug code

* Spannified GCD algorithm

* Removed arg passing by ref

* Complete migration of gcd to span

* Removed memory allocation for trivial case of GCD

* Reorganize utility code

* Removed redundant parameter

* Reduced memory alloc for trivial case of Pow alg

* Use span for trivial modulus pow case

* Use span for trivial cases of pow modulus

* Fixed buffer cleanup

* Reduced memory allocation of ModPow operations

* BitsBuffer replaced with span

* Reorganize utility methods

* Reduced visibility scope

* Removed temporary code for bits cleanup

* Fill leading bits with zeroes

* Unify order of parameters

* Unify order of parameters

* Unify order of parameters

* Reuse the same local var

* Removed whtespace

* Code review feedback

* Avoid misleading with Span.CopyTo

* Clarify purpose of slicing

Co-authored-by: Günther Foidl <[email protected]>

* Replace mentioning of uint[] in comments

* Fixed code formatting issues

* Share StackAllocThreshold const

* Ignore const threshold values in Release config

* Literal field cannot be discovered with TypeInfo.GetDeclaredField

* Removed invalid assertion

* Removed invalid assertion

* Removed invalid assertion

* Code review feedback

* Fixed header

* Fixed overflow of result buffer

* Reduced ctor visibility

* Eliminated bounds check

* Returned optimization using Math.DivRem

* Added braces to be consistent with other branches

* Fixed location of the comment

* Combine slice and conversion as the single line

* Combine slice and conversion as the single line

* Merge with parsing optimizations

* Reduced checks

* Removed redundant global const

* Replaced magic consts with their named equivalents

* Removed useless asserts

* Manual merge with #53984 PR

* Review feedback (use const for stackalloc)

* Review feedback

* Unsafe code replaced with span indexer where possible

* Removed unused local

* Review feedback

Co-authored-by: Günther Foidl <[email protected]>
  • Loading branch information
sakno and gfoidl authored Oct 8, 2021
1 parent 3649506 commit 22b516c
Show file tree
Hide file tree
Showing 15 changed files with 1,374 additions and 1,409 deletions.
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>
871 changes: 573 additions & 298 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,112 +2,89 @@
// 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 >= 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 >= right.Length);

int i = 0;
long carry = 0L;

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

// 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;
Expand All @@ -117,110 +94,84 @@ private static unsafe void AddSelf(uint* left, int leftLength,
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 >= 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 >= 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);

// 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;
Expand All @@ -229,48 +180,5 @@ private static unsafe void SubtractSelf(uint* left, int leftLength,

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

0 comments on commit 22b516c

Please sign in to comment.