未验证 提交 2f843a8f 编写于 作者: A Alexander Speshilov 提交者: GitHub

Use Span.CopyTo(Span) in BigInteger's add and subtract (#83951), fixes #83457

* Use Span.CopyTo(Span) in BigInteger's add and subtract

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.
See #83457 for details.
Co-authored-by: NAdam Sitnik <adam.sitnik@gmail.com>
上级 d39c2eb0
......@@ -9,58 +9,51 @@ namespace System.Numerics
{
internal static partial class BigIntegerCalculator
{
private const 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);
// 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.
long carry = right;
for (int i = 0; i < left.Length; i++)
{
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
carry = digit >> 32;
}
bits[left.Length] = (uint)carry;
Add(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialCarry: right);
}
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);
// index bounds check for all buffers.
ref uint resultPtr = ref MemoryMarshal.GetReference(bits);
ref uint rightPtr = ref MemoryMarshal.GetReference(right);
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
int i = 0;
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++)
{
long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
for ( ; i < left.Length; i++)
do
{
long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
Unsafe.Add(ref resultPtr, i) = (uint)carry;
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 < right.Length);
Add(left, bits, ref resultPtr, startIndex: i, initialCarry: carry);
}
private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
......@@ -100,53 +93,40 @@ 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.
// Thus, we've similar code than below, but there is no loop for
// processing the 32-bit integer, since it's a single element.
long carry = -right;
for (int i = 0; i < left.Length; i++)
{
long digit = left[i] + carry;
bits[i] = unchecked((uint)digit);
carry = digit >> 32;
}
Subtract(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialCarry: -right);
}
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);
// index bounds check for all buffers.
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.
int i = 0;
long carry = 0;
for ( ; i < right.Length; i++)
{
long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
carry = digit >> 32;
}
for ( ; i < left.Length; i++)
{
long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
}
// 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.
Debug.Assert(carry == 0);
do
{
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 < right.Length);
Subtract(left, bits, ref resultPtr, startIndex: i, initialCarry: carry);
}
private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
......@@ -180,5 +160,95 @@ private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
Debug.Assert(carry == 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Add(ReadOnlySpan<uint> left, Span<uint> bits, ref uint resultPtr, int startIndex, long initialCarry)
{
// 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.
int i = startIndex;
long carry = initialCarry;
if (left.Length <= CopyToThreshold)
{
for (; i < left.Length; i++)
{
carry += left[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}
Unsafe.Add(ref resultPtr, left.Length) = unchecked((uint)carry);
}
else
{
for (; i < left.Length;)
{
carry += left[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
i++;
carry >>= 32;
// Once carry is set to 0 it can not be 1 anymore.
// So the tail of the loop is just the movement of argument values to result span.
if (carry == 0)
{
break;
}
}
Unsafe.Add(ref resultPtr, left.Length) = unchecked((uint)carry);
if (i < left.Length)
{
CopyTail(left, bits, i);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Subtract(ReadOnlySpan<uint> left, Span<uint> bits, ref uint resultPtr, int startIndex, long initialCarry)
{
// 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.
int i = startIndex;
long carry = initialCarry;
if (left.Length <= CopyToThreshold)
{
for (; i < left.Length; i++)
{
carry += left[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
carry >>= 32;
}
}
else
{
for (; i < left.Length;)
{
carry += left[i];
Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry);
i++;
carry >>= 32;
// Once carry is set to 0 it can not be 1 anymore.
// So the tail of the loop is just the movement of argument values to result span.
if (carry == 0)
{
break;
}
}
if (i < left.Length)
{
CopyTail(left, bits, i);
}
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册