Skip to content

Commit 22b516c

Browse files
saknogfoidl
andauthored
Spannified internals of BigInteger (#35565)
* 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 <gue@korporal.at> * 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 <gue@korporal.at>
1 parent 3649506 commit 22b516c

15 files changed

Lines changed: 1374 additions & 1409 deletions

src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
</PropertyGroup>
88
<ItemGroup>
99
<Compile Include="System\Numerics\BigIntegerCalculator.AddSub.cs" />
10-
<Compile Include="System\Numerics\BigIntegerCalculator.BitsBuffer.cs" />
1110
<Compile Include="System\Numerics\BigIntegerCalculator.DivRem.cs" />
1211
<Compile Include="System\Numerics\BigIntegerCalculator.FastReducer.cs" />
1312
<Compile Include="System\Numerics\BigIntegerCalculator.GcdInv.cs" />
1413
<Compile Include="System\Numerics\BigIntegerCalculator.PowMod.cs" />
1514
<Compile Include="System\Numerics\BigIntegerCalculator.SquMul.cs" />
15+
<Compile Include="System\Numerics\BigIntegerCalculator.Utils.cs" />
1616
<Compile Include="System\Numerics\BigInteger.cs" />
1717
<Compile Include="System\Numerics\BigNumber.cs" />
1818
<Compile Include="System\Numerics\NumericsHelpers.cs" />
@@ -30,5 +30,6 @@
3030
<Reference Include="System.Memory" />
3131
<Reference Include="System.Runtime" />
3232
<Reference Include="System.Runtime.Extensions" />
33+
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
3334
</ItemGroup>
3435
</Project>

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs

Lines changed: 573 additions & 298 deletions
Large diffs are not rendered by default.

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.AddSub.cs

Lines changed: 67 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -2,112 +2,89 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5-
using System.Security;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
67

78
namespace System.Numerics
89
{
910
internal static partial class BigIntegerCalculator
1011
{
11-
public static uint[] Add(uint[] left, uint right)
12+
public static void Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits)
1213
{
13-
Debug.Assert(left != null);
1414
Debug.Assert(left.Length >= 1);
15+
Debug.Assert(bits.Length == left.Length + 1);
1516

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

20-
uint[] bits = new uint[left.Length + 1];
21+
long carry = right;
2122

22-
long digit = (long)left[0] + right;
23-
bits[0] = unchecked((uint)digit);
24-
long carry = digit >> 32;
25-
26-
for (int i = 1; i < left.Length; i++)
23+
for (int i = 0; i < left.Length; i++)
2724
{
28-
digit = left[i] + carry;
25+
long digit = left[i] + carry;
2926
bits[i] = unchecked((uint)digit);
3027
carry = digit >> 32;
3128
}
32-
bits[left.Length] = (uint)carry;
3329

34-
return bits;
30+
bits[left.Length] = (uint)carry;
3531
}
3632

37-
public static unsafe uint[] Add(uint[] left, uint[] right)
33+
public static void Add(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
3834
{
39-
Debug.Assert(left != null);
40-
Debug.Assert(right != null);
4135
Debug.Assert(left.Length >= right.Length);
36+
Debug.Assert(bits.Length == left.Length + 1);
4237

43-
// Switching to unsafe pointers helps sparing
44-
// some nasty index calculations...
45-
46-
uint[] bits = new uint[left.Length + 1];
47-
48-
fixed (uint* l = left, r = right, b = &bits[0])
49-
{
50-
Add(l, left.Length,
51-
r, right.Length,
52-
b, bits.Length);
53-
}
54-
55-
return bits;
56-
}
38+
int i = 0;
39+
long carry = 0L;
5740

58-
private static unsafe void Add(uint* left, int leftLength,
59-
uint* right, int rightLength,
60-
uint* bits, int bitsLength)
61-
{
62-
Debug.Assert(leftLength >= 0);
63-
Debug.Assert(rightLength >= 0);
64-
Debug.Assert(leftLength >= rightLength);
65-
Debug.Assert(bitsLength == leftLength + 1);
41+
// Switching to managed references helps eliminating
42+
// index bounds check...
43+
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
44+
ref uint resultPtr = ref MemoryMarshal.GetReference(bits);
6645

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

72-
int i = 0;
73-
long carry = 0L;
74-
75-
for (; i < rightLength; i++)
51+
for ( ; i < right.Length; i++)
7652
{
77-
long digit = (left[i] + carry) + right[i];
78-
bits[i] = unchecked((uint)digit);
53+
long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i];
54+
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
7955
carry = digit >> 32;
8056
}
81-
for (; i < leftLength; i++)
57+
for ( ; i < left.Length; i++)
8258
{
8359
long digit = left[i] + carry;
84-
bits[i] = unchecked((uint)digit);
60+
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
8561
carry = digit >> 32;
8662
}
87-
bits[i] = (uint)carry;
63+
Unsafe.Add(ref resultPtr, i) = (uint)carry;
8864
}
8965

90-
private static unsafe void AddSelf(uint* left, int leftLength,
91-
uint* right, int rightLength)
66+
private static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
9267
{
93-
Debug.Assert(leftLength >= 0);
94-
Debug.Assert(rightLength >= 0);
95-
Debug.Assert(leftLength >= rightLength);
68+
Debug.Assert(left.Length >= right.Length);
69+
70+
int i = 0;
71+
long carry = 0L;
72+
73+
// Switching to managed references helps eliminating
74+
// index bounds check...
75+
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
9676

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

101-
int i = 0;
102-
long carry = 0L;
103-
104-
for (; i < rightLength; i++)
81+
for ( ; i < right.Length; i++)
10582
{
106-
long digit = (left[i] + carry) + right[i];
107-
left[i] = unchecked((uint)digit);
83+
long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i];
84+
Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit);
10885
carry = digit >> 32;
10986
}
110-
for (; carry != 0 && i < leftLength; i++)
87+
for ( ; carry != 0 && i < left.Length; i++)
11188
{
11289
long digit = left[i] + carry;
11390
left[i] = (uint)digit;
@@ -117,110 +94,84 @@ private static unsafe void AddSelf(uint* left, int leftLength,
11794
Debug.Assert(carry == 0);
11895
}
11996

120-
public static uint[] Subtract(uint[] left, uint right)
97+
public static void Subtract(ReadOnlySpan<uint> left, uint right, Span<uint> bits)
12198
{
122-
Debug.Assert(left != null);
12399
Debug.Assert(left.Length >= 1);
124100
Debug.Assert(left[0] >= right || left.Length >= 2);
101+
Debug.Assert(bits.Length == left.Length);
125102

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

130-
uint[] bits = new uint[left.Length];
107+
long carry = -right;
131108

132-
long digit = (long)left[0] - right;
133-
bits[0] = unchecked((uint)digit);
134-
long carry = digit >> 32;
135-
136-
for (int i = 1; i < left.Length; i++)
109+
for (int i = 0; i < left.Length; i++)
137110
{
138-
digit = left[i] + carry;
111+
long digit = left[i] + carry;
139112
bits[i] = unchecked((uint)digit);
140113
carry = digit >> 32;
141114
}
142-
143-
return bits;
144115
}
145116

146-
public static unsafe uint[] Subtract(uint[] left, uint[] right)
117+
public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
147118
{
148-
Debug.Assert(left != null);
149-
Debug.Assert(right != null);
150119
Debug.Assert(left.Length >= right.Length);
151120
Debug.Assert(Compare(left, right) >= 0);
121+
Debug.Assert(bits.Length == left.Length);
152122

153-
// Switching to unsafe pointers helps sparing
154-
// some nasty index calculations...
155-
156-
uint[] bits = new uint[left.Length];
157-
158-
fixed (uint* l = left, r = right, b = bits)
159-
{
160-
Subtract(l, left.Length,
161-
r, right.Length,
162-
b, bits.Length);
163-
}
164-
165-
return bits;
166-
}
123+
int i = 0;
124+
long carry = 0L;
167125

168-
private static unsafe void Subtract(uint* left, int leftLength,
169-
uint* right, int rightLength,
170-
uint* bits, int bitsLength)
171-
{
172-
Debug.Assert(leftLength >= 0);
173-
Debug.Assert(rightLength >= 0);
174-
Debug.Assert(leftLength >= rightLength);
175-
Debug.Assert(Compare(left, leftLength, right, rightLength) >= 0);
176-
Debug.Assert(bitsLength == leftLength);
126+
// Switching to managed references helps eliminating
127+
// index bounds check...
128+
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
129+
ref uint resultPtr = ref MemoryMarshal.GetReference(bits);
177130

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

183-
int i = 0;
184-
long carry = 0L;
185-
186-
for (; i < rightLength; i++)
136+
for ( ; i < right.Length; i++)
187137
{
188-
long digit = (left[i] + carry) - right[i];
189-
bits[i] = unchecked((uint)digit);
138+
long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i];
139+
Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit);
190140
carry = digit >> 32;
191141
}
192-
for (; i < leftLength; i++)
142+
for ( ; i < left.Length; i++)
193143
{
194144
long digit = left[i] + carry;
195-
bits[i] = (uint)digit;
145+
Unsafe.Add(ref resultPtr, i) = (uint)digit;
196146
carry = digit >> 32;
197147
}
198148

199149
Debug.Assert(carry == 0);
200150
}
201151

202-
private static unsafe void SubtractSelf(uint* left, int leftLength,
203-
uint* right, int rightLength)
152+
private static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
204153
{
205-
Debug.Assert(leftLength >= 0);
206-
Debug.Assert(rightLength >= 0);
207-
Debug.Assert(leftLength >= rightLength);
208-
Debug.Assert(Compare(left, leftLength, right, rightLength) >= 0);
154+
Debug.Assert(left.Length >= right.Length);
155+
Debug.Assert(Compare(left, right) >= 0);
156+
157+
int i = 0;
158+
long carry = 0L;
159+
160+
// Switching to managed references helps eliminating
161+
// index bounds check...
162+
ref uint leftPtr = ref MemoryMarshal.GetReference(left);
209163

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

214-
int i = 0;
215-
long carry = 0L;
216-
217-
for (; i < rightLength; i++)
168+
for (; i < right.Length; i++)
218169
{
219-
long digit = (left[i] + carry) - right[i];
220-
left[i] = unchecked((uint)digit);
170+
long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i];
171+
Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit);
221172
carry = digit >> 32;
222173
}
223-
for (; carry != 0 && i < leftLength; i++)
174+
for (; carry != 0 && i < left.Length; i++)
224175
{
225176
long digit = left[i] + carry;
226177
left[i] = (uint)digit;
@@ -229,48 +180,5 @@ private static unsafe void SubtractSelf(uint* left, int leftLength,
229180

230181
Debug.Assert(carry == 0);
231182
}
232-
233-
public static int Compare(uint[] left, uint[] right)
234-
{
235-
Debug.Assert(left != null);
236-
Debug.Assert(right != null);
237-
238-
if (left.Length < right.Length)
239-
return -1;
240-
if (left.Length > right.Length)
241-
return 1;
242-
243-
for (int i = left.Length - 1; i >= 0; i--)
244-
{
245-
if (left[i] < right[i])
246-
return -1;
247-
if (left[i] > right[i])
248-
return 1;
249-
}
250-
251-
return 0;
252-
}
253-
254-
private static unsafe int Compare(uint* left, int leftLength,
255-
uint* right, int rightLength)
256-
{
257-
Debug.Assert(leftLength >= 0);
258-
Debug.Assert(rightLength >= 0);
259-
260-
if (leftLength < rightLength)
261-
return -1;
262-
if (leftLength > rightLength)
263-
return 1;
264-
265-
for (int i = leftLength - 1; i >= 0; i--)
266-
{
267-
if (left[i] < right[i])
268-
return -1;
269-
if (left[i] > right[i])
270-
return 1;
271-
}
272-
273-
return 0;
274-
}
275183
}
276184
}

0 commit comments

Comments
 (0)