Skip to content

Commit

Permalink
Use Span.CopyTo(Span) in BigInteger's add and subtract
Browse files Browse the repository at this point in the history
In BigIntegerCalculator methods Add and Subtract, if sizes of arguments
differ, after processing the part of size of the right (small) argument,
there was loop of add/sub carry value. When the carry value once become
zero, in fact the rest of the larger argument can be copied to the result.

With this commit the second loop is interrupted when carry become zero
and applies fast Span.CopyTo(Span) to the rest part.

This optimization applied only when size of the greatest argument is more
or equal to const CopyToThreshold introduced in this commit. This const
is 8 now.

Also made minor related changes to hot cycles.

See dotnet#83457 for details.
  • Loading branch information
speshuric committed Apr 11, 2023
1 parent 49b68a9 commit d58d9f9
Showing 1 changed file with 163 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,131 @@ namespace System.Numerics
{
internal static partial class BigIntegerCalculator
{

#if DEBUG
// Mutable for unit testing.
private static
#else
private const
#endif
int CopyToThreshold = 8;

private static void CopyTail(ReadOnlySpan<uint> source, Span<uint> dest, int start)
{
source.Slice(start).CopyTo(dest.Slice(start));
}

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

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

// 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.

nint i = 0;
nint upperBound = left.Length;
long carry = right;

for (int i = 0; i < left.Length; i++)
if (upperBound <= CopyToThreshold)
{
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
carry = digit >> 32;
for ( ; i < upperBound; i++)
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}

Unsafe.Add(ref resultPtr, upperBound) = unchecked((uint)carry);
}
else
{
for ( ; i < upperBound; )
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
i++;
carry >>= 32;
if (carry == 0) break;
}

Unsafe.Add(ref resultPtr, upperBound) = unchecked((uint)carry);

if (i < upperBound)
{
CopyTail(left, bits, unchecked((int)i));
}
}

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

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

int i = 0;
long carry = 0L;

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

nint i = 0;
nint upperBound = right.Length;
long carry = 0;

// 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.

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

if (upperBound <= CopyToThreshold)
{
for ( ; i < upperBound; i++)
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}

Unsafe.Add(ref resultPtr, upperBound) = unchecked((uint)carry);
}
for ( ; i < left.Length; i++)
else
{
long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
for ( ; i < upperBound; )
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
i++;
carry >>= 32;
if (carry == 0) break;
}

Unsafe.Add(ref resultPtr, upperBound) = unchecked((uint)carry);

if (i < upperBound)
{
CopyTail(left, bits, unchecked((int)i));
}
}
Unsafe.Add(ref resultPtr, i) = (uint)carry;

}

private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
Expand Down Expand Up @@ -100,53 +173,103 @@ public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits
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.
// Switching to managed references helps eliminating
// index bounds check...
ref uint resultPtr = ref MemoryMarshal.GetReference(bits);
ref uint leftPtr = ref MemoryMarshal.GetReference(left);

// 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.

nint i = 0;
nint upperBound = left.Length;
long carry = -right;

for (int i = 0; i < left.Length; i++)
if (upperBound <= CopyToThreshold)
{
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
carry = digit >> 32;
for ( ; i < upperBound; i++)
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}
}
else
{
for ( ; i < upperBound; )
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
i++;
carry >>= 32;
if (carry == 0) break;
}

if (i < upperBound)
{
CopyTail(left, bits, unchecked((int)i));
}
}

}

public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
{
Debug.Assert(right.Length >= 1);
Debug.Assert(left.Length >= right.Length);
Debug.Assert(Compare(left, right) >= 0);
Debug.Assert(bits.Length == left.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 resultPtr = ref MemoryMarshal.GetReference(bits);
ref uint rightPtr = ref MemoryMarshal.GetReference(right);
ref uint leftPtr = ref MemoryMarshal.GetReference(left);

// 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.
nint i = 0;
nint upperBound = right.Length;
long carry = 0;

for ( ; i < right.Length; i++)
// 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.

do
{
long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
carry += Unsafe.Add(ref leftPtr, i);
carry -= Unsafe.Add(ref rightPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
i++;
} while (i < upperBound);
upperBound = left.Length;

if (upperBound <= CopyToThreshold)
{
for ( ; i < upperBound; i++)
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}
}
for ( ; i < left.Length; i++)
else
{
long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)
{
carry += Unsafe.Add(ref leftPtr, i);
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}

if (i < upperBound)
{
CopyTail(left, bits, unchecked((int)i));
}
}

Debug.Assert(carry == 0);
}

private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
Expand Down

0 comments on commit d58d9f9

Please sign in to comment.