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

Optimize BigInteger.Divide #96895

Merged
merged 31 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
919f4e6
Add divide tests
kzrnm Jan 5, 2024
e5948e9
Divide by Burnikel-Ziegler algorithm
kzrnm Jan 5, 2024
e84cbb6
Copy from UInt128
kzrnm Jan 12, 2024
d270ae8
fix
kzrnm Jan 12, 2024
8d5040b
Test
kzrnm Jan 12, 2024
cfcd62e
Assert(CompareActual())
kzrnm Jan 12, 2024
7d64f49
OuterLoop
kzrnm Jan 12, 2024
c72c0b6
RunWithFakeThreshold
kzrnm Jan 12, 2024
48c2f75
Use ActualLength
kzrnm Jan 21, 2024
29f2fb1
Clear
kzrnm Jan 25, 2024
4f087e3
Merge branch 'main' into BigIntegerDivide
tannergooding Jan 29, 2024
e9d3d1e
Unnecessary if
kzrnm Feb 2, 2024
79ed99a
Remove unreachable path
kzrnm Feb 17, 2024
b435d69
Merge branch 'main' into BigIntegerDivide
kzrnm Feb 17, 2024
4f60999
Merge remote-tracking branch 'upstream/main' into BigIntegerDivide
kzrnm Feb 17, 2024
d520e5b
Fix
kzrnm Feb 18, 2024
fca7589
DivideGrammarSchool if n is less than or equals to DivideThreshold
kzrnm Feb 18, 2024
6ac27a6
Fix D3n2n
kzrnm Feb 19, 2024
003d1c9
DivideBurnikelZieglerThreshold
kzrnm Feb 19, 2024
2a462e0
remainder
kzrnm Feb 19, 2024
652f17d
halfN → n
kzrnm Feb 20, 2024
bfc39f7
DisableParallelization
kzrnm Feb 20, 2024
b66d885
Merge branch 'main' into BigIntegerDivide
kzrnm Jul 10, 2024
9ffb4d3
Merge remote-tracking branch 'upstream/main' into BigIntegerDivide
kzrnm Aug 12, 2024
0288d40
Merge branch 'main' into BigIntegerDivide
danmoseley Dec 2, 2024
c71a482
Merge remote-tracking branch 'dotnet/main' into BigIntegerDivide
tannergooding Feb 3, 2025
801462a
Resolve merge conflicts
tannergooding Feb 3, 2025
b1122cf
Add brace
kzrnm Feb 4, 2025
0b57afb
Rename DummyForDebug to InitializeForDebug
kzrnm Feb 4, 2025
6c61159
Add comment
kzrnm Feb 4, 2025
9ec331c
Fix format
kzrnm Feb 4, 2025
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 @@ -71,13 +71,13 @@ public static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
// 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.

for ( ; i < right.Length; i++)
for (; i < right.Length; i++)
{
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 < left.Length; i++)
for (; carry != 0 && i < left.Length; i++)
{
long digit = left[i] + carry;
left[i] = (uint)digit;
Expand All @@ -100,7 +100,7 @@ public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, S
{
Debug.Assert(right.Length >= 1);
Debug.Assert(left.Length >= right.Length);
Debug.Assert(Compare(left, right) >= 0);
Debug.Assert(CompareActual(left, right) >= 0);
Debug.Assert(bits.Length == left.Length);

// Switching to managed references helps eliminating
Expand Down Expand Up @@ -132,8 +132,9 @@ public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, S
public static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
{
Debug.Assert(left.Length >= right.Length);

// Assertion failing per https://github.com/dotnet/runtime/issues/97780
// Debug.Assert(Compare(left, right) >= 0);
// Debug.Assert(CompareActual(left, right) >= 0);

int i = 0;
long carry = 0L;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public FastReducer(ReadOnlySpan<uint> modulus, Span<uint> r, Span<uint> mu, Span
r[r.Length - 1] = 1;

// Let mu = 4^k / m
Divide(r, modulus, mu);
DivRem(r, modulus, mu);
_modulus = modulus;

_q1 = q1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static void Gcd(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<u
{
Debug.Assert(left.Length >= 2);
Debug.Assert(right.Length >= 2);
Debug.Assert(Compare(left, right) >= 0);
Debug.Assert(CompareActual(left, right) >= 0);
Debug.Assert(result.Length == left.Length);

left.CopyTo(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ stackalloc uint[StackAllocThreshold]

if (value.Length > modulus.Length)
{
Remainder(value, modulus, valueCopy);
Remainder(value, modulus, valueCopy.Slice(0, value.Length));
}
else
{
Expand Down Expand Up @@ -276,7 +276,7 @@ stackalloc uint[StackAllocThreshold]

if (value.Length > modulus.Length)
{
Remainder(value, modulus, valueCopy);
Remainder(value, modulus, valueCopy.Slice(0, value.Length));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ public static int Compare(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right)
return left[iv] < right[iv] ? -1 : 1;
}

private static int CompareActual(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right)
{
if (left.Length != right.Length)
{
if (left.Length < right.Length)
{
if (ActualLength(right.Slice(left.Length)) > 0)
return -1;
right = right.Slice(0, left.Length);
}
else
{
if (ActualLength(left.Slice(right.Length)) > 0)
return +1;
left = left.Slice(0, right.Length);
}
}
return Compare(left, right);
}

public static int ActualLength(ReadOnlySpan<uint> value)
{
// Since we're reusing memory here, the actual length
Expand All @@ -49,11 +69,18 @@ private static int Reduce(Span<uint> bits, ReadOnlySpan<uint> modulus)

if (bits.Length >= modulus.Length)
{
Divide(bits, modulus, default);
DivRem(bits, modulus, default);

return ActualLength(bits.Slice(0, modulus.Length));
}
return bits.Length;
}

[Conditional("DEBUG")]
public static void InitializeForDebug(Span<uint> bits)
{
// Reproduce the case where the return value of `stackalloc uint` is not initialized to zero.
bits.Fill(0xCD);
}
}
}
56 changes: 56 additions & 0 deletions src/libraries/System.Runtime.Numerics/tests/BigInteger/divide.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ public static void RunDivideTwoLargeBI()
}
}

[Fact]
public static void RunDivideOneLargeOneHalfBI()
{
byte[] tempByteArray1 = new byte[0];
byte[] tempByteArray2 = new byte[0];

// Divide Method - Two Large BigIntegers
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
tempByteArray1 = GetRandomByteArray(s_random, 512 + i);
tempByteArray2 = GetRandomByteArray(s_random, 256 + j);
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivide");
}
}

[Fact]
public static void RunDivideTwoSmallBI()
{
Expand Down Expand Up @@ -149,6 +165,31 @@ public static void RunDivideBoundary()
VerifyDivideString(Math.Pow(2, 33) + " 2 bDivide");
}

[Fact]
public static void RunDivideLarge()
{
byte[] tempByteArray1 = new byte[0];
byte[] tempByteArray2 = new byte[0];

for (int i = 1; i < 15; i += 3)
{
var num = 1 << i;
tempByteArray1 = GetRandomByteArray(s_random, num);
tempByteArray2 = GetRandomByteArray(s_random, num / 2);
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivide");
}

// Divide Method - Two Large BigIntegers
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
int num = (1 << 13) + i;
tempByteArray1 = GetRandomByteArray(s_random, num);
tempByteArray2 = GetRandomByteArray(s_random, num / 3 + j);
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivide");
}
}

[Fact]
public static void RunOverflow()
{
Expand All @@ -161,6 +202,21 @@ public static void RunOverflow()
Assert.Equal(z, BigInteger.Divide(x, y));
}

[Fact]
public void D3n2nBound()
{
var right = (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 4 * 32 - 1))
+ (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 2 * 32)) - 1;
var rem = right - 1;

var qi = BigIntegerCalculator.DivideBurnikelZieglerThreshold * 8 * 32 * 4 - 1;
var q = (BigInteger.One << qi) - 1;
var left = q * right + rem;

var q2 = BigInteger.Divide(left, right);
Assert.Equal(q, q2);
}

private static void VerifyDivideString(string opstring)
{
StackCalc sc = new StackCalc(opstring);
Expand Down
95 changes: 95 additions & 0 deletions src/libraries/System.Runtime.Numerics/tests/BigInteger/divrem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ public static void RunDivRem_OneSmallOneLargeBI()
}
}

[Fact]
public static void RunDivRem_OneLargeOneHalfBI()
{
byte[] tempByteArray1 = new byte[0];
byte[] tempByteArray2 = new byte[0];

// Divide Method - Two Large BigIntegers
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
tempByteArray1 = GetRandomByteArray(s_random, 512 + i);
tempByteArray2 = GetRandomByteArray(s_random, 256 + j);
VerifyDivRemString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivRem");
}
}

[Fact]
public static void RunDivRem_OneLargeOne0BI()
{
Expand Down Expand Up @@ -114,6 +130,73 @@ public static void Boundary()
VerifyDivRemString(Math.Pow(2, 33) + " 2 bDivRem");
}

[Fact]
public static void RunDivRemMedium()
{
byte[] tempByteArray1 = new byte[0];
byte[] tempByteArray2 = new byte[0];

for (int i = 1; i < 8; i++)
{
var num = 1 << i;
tempByteArray1 = GetRandomByteArray(s_random, num);
tempByteArray2 = GetRandomByteArray(s_random, num / 2);
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
}

// Divide Method - Two Large BigIntegers
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
int num = (1 << 7) + i;
tempByteArray1 = GetRandomByteArray(s_random, num);
tempByteArray2 = GetRandomByteArray(s_random, num / 3 + j);
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
}
}

[Fact]
[OuterLoop]
public static void RunDivRemLarge()
{
byte[] tempByteArray1 = new byte[0];
byte[] tempByteArray2 = new byte[0];

for (int i = 8; i < 10; i++)
{
var num = 1 << i;
tempByteArray1 = GetRandomByteArray(s_random, num);
tempByteArray2 = GetRandomByteArray(s_random, num / 2);
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
}

// Divide Method - Two Large BigIntegers
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
int num = (1 << 10) + i;
tempByteArray1 = GetRandomByteArray(s_random, num);
tempByteArray2 = GetRandomByteArray(s_random, num / 3 + j);
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
}
}

[Fact]
public void D3n2nBound()
{
var right = (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 4 * 32 - 1))
+ (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 2 * 32)) - 1;
var rem = right - 1;

var qi = BigIntegerCalculator.DivideBurnikelZieglerThreshold * 8 * 32 * 4 - 1;
var q = (BigInteger.One << qi) - 1;
var left = q * right + rem;

var (q2, r2) = BigInteger.DivRem(left, right);
Assert.Equal(q, q2);
Assert.Equal(rem, r2);
}

[Fact]
public static void RunDivRemTests()
{
Expand Down Expand Up @@ -211,4 +294,16 @@ private static string Print(byte[] bytes)
return MyBigIntImp.Print(bytes);
}
}


[Collection(nameof(DisableParallelization))]
public class divremTestThreshold
{
[Fact]
public void RunDivRemMedium()
{
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.DivideBurnikelZieglerThreshold, 8, divremTest.RunDivRemMedium);
}

}
}
25 changes: 25 additions & 0 deletions src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,31 @@ public static void ModPow2Large1SmallInt()
}
}

// InlineData randomly generated using a new Random(0) and the same logic as is used in MyBigIntImp
// When using the VerifyModPowString approach, these tests were taking over 100s to execute.
[Theory]
[OuterLoop]
[InlineData("16152447169612934532253858872860101823992426489763666485380167221632010974596350526903901586786157942589749704137347759619657187784260082814484793173551647870373927196771852", "81750440863317178198977948957223170296434385667401518194303765341584160931262254891905182291359611229458113456891057547210603179481274334549407149567413965138338569615186383", "-7196805437625531929619339290119232497987723600413951949478998858079935202962418871178150151495255147489121599998392939293913217145771279946557062800866475934948778", "5616380469802854111883204613624062553120851412450654399190717359559254034938493684003207209263083893212739965150170342564431867389596229480294680122528026782956958")]
[InlineData("-19804882257271871615998867413310154569092691610863183900352723872936597192840601413521317416195627481021453523098672122338092864102790996172", "5005126326020660905045117276428380928616741884628331700680598638081087432348216495484429178211470872172882902036752474804904132643", "-103139435102033366056363338063498713727200190198435", "-4559679593639479333979462122256288215243720737403")]
[InlineData("-2263742881720266366742488789295767051326353176981812961528848101545308514727342989711034207353102864275894987436688194776201313772226059035143797121004935923223042331190890429211181925543749113890129660515170733848725", "313166794469483345944045915670847620773229708424183728974404367769353433443313247319899209581239311758905801464781585268041623664425", "3984523819293796257818294503433333109083365267654089093971156331037874112339941681299823291492643701164442964256327008451060278818307268485187524722068240", "-1502346344475556570236985614111713440763176490652894928852811056060906839905964408583918853958610145894621840382970373570196361549098246030413222124498085")]
[InlineData("-342701069914551895507647608205424602441707687704978754182486401529587201154652163656221404036731562708712821963465111719289193200432064875769386559645346920", "247989781302056934762335026151076445832166884867735502165354252207668083157132317377069604102049233014316799294014890817943261246892765157594412634897440785353202366563028", "121555428622583377664148832113619145387775383377032217138159544127299380518157949963314283123957062240152711187509503414343", "87578369862034238407481381238856926729112161247036763388287150463197193460326629595765471822752579542310337770783772458710")]
[InlineData("-282593950368131775131360433563237877977879464854725217398276355042086422366377452969365517205334520940629323311057746859", "5959258935361466196732788139899933762310441595693546573755448590100882267274831199165902682626769861372370905838130200967035", "6598019436100687108279703832125132357070343951841815860721553173215685978621505459125000339496396952653080051757197617932586296524960251609958919233462530", "-4035534917213670830138661334307123840766321272945538719146336835321463334263841831803093764143165039453996360590495570418141622764990299884920213157241339")]
[InlineData("-283588760164723199492097446398514238808996680579814508529658395835708678571214111525627362048679162021949222615325057958783388520044013697149834530411072380435126870273057157745943859", "1042611904427950637176782337251399378305726352782300537151930702574076654415735617544217054055762002017753284951033975382044655538090809873113604", "11173562248848713955826639654969725554069867375462328112124015145073186375237811117727665778232780449525476829715663254692620996726130822561707626585790443774041565237684936409844925424596571418502946", "6662129352542591544500713232459850446949913817909326081510506555775206102887692404112984526760120407457772674917650956873499346517965094865621779695963015030158124625116211104048940313058280680420919")]
[InlineData("683399436848090272429540515929404372035986606104192913128573936597145312908480564700882440819526500604918037963508399983297602351856208190565527", "223751878996658298590720798129833935741775535718632942242965085592936259576946732440406302671204348239437556817289012497483482656", "1204888420642360606571457515385663952017382888522547766961071698778167608427220546474854934298311882921224875807375321847274969073309433075566441363244101914489524538123410250010519308563731930923389473186", "1136631484875680074951300738594593722168933395227758228596156355418704717505715681950525129323209331607163560404958604424924870345828742295978850144844693079191828839673460389985036424301333691983679427765")]
[InlineData("736513799451968530811005754031332418210960966881742655756522735504778110620671049112529346250333710388060811959329786494662578020803", "2461175085563866950903873687720858523536520498137697316698237108626602445202960480677695918813575265778826908481129155012799", "-4722693720735888562993277045098354134891725536023070176847814685098361292027040929352405620815883795027263132404351040", "4351573186631261607388198896754285562669240685903971199359912143458682154189588696264319780329366022294935204028039787")]
[InlineData("1596188639947986471148999794547338", "685242191738212089917782567856594513073397739443", "41848166029740752457613562518205823134173790454761036532025758411484449588176128053901271638836032557551179866133091058357374964041641117585422447497779410336188602585660372002644517668041207657383104649333118253", "39246949850380693159338034407642149926180988060650630387722725303281343126585456713282439764667310808891687831648451269002447916277601468727040185218264602698691553232132525542650964722093335105211816394635493987")]
[InlineData("-1506852741293695463963822334869441845197951776565891060639754936248401744065969556756496718308248025911048010080232290368562210204958094544173209793990218122", "64905085725614938357105826012272472070910693443851911667721848542473785070747281964799379996923261457185847459711", "2740467233603031668807697475486217767705051", "-1905434239471820365929630558127219204166613")]
public static void ModPow3LargeInt(string value, string exponent, string modulus, string expected)
{
BigInteger valueInt = BigInteger.Parse(value);
BigInteger exponentInt = BigInteger.Parse(exponent);
BigInteger modulusInt = BigInteger.Parse(modulus);
BigInteger resultInt = BigInteger.Parse(expected);

// Once with default threshold
Assert.Equal(resultInt, BigInteger.ModPow(valueInt, exponentInt, modulusInt));
}

[Fact]
public static void ModPow0Power()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ public static void RunDividePositivenonZero()
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "b/");
}

// Divide Method - One large and one half BigIntegers
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
tempByteArray1 = GetRandomByteArray(s_random, 512 + i);
tempByteArray2 = GetRandomByteArray(s_random, 256 + j);
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "b/");
}

// Divide Method - One large and one small BigIntegers
for (int i = 0; i < s_samples; i++)
{
Expand Down Expand Up @@ -140,6 +149,21 @@ public static void RunOverflow()
Assert.Equal(z, x / y);
}

[Fact]
public void D3n2nBound()
{
var right = (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 4 * 32 - 1))
+ (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 2 * 32)) - 1;
var rem = right - 1;

var qi = BigIntegerCalculator.DivideBurnikelZieglerThreshold * 8 * 32 * 4 - 1;
var q = (BigInteger.One << qi) - 1;
var left = q * right + rem;

var q2 = left / right;
Assert.Equal(q, q2);
}

private static void VerifyDivideString(string opstring)
{
StackCalc sc = new StackCalc(opstring);
Expand Down
Loading