Skip to content

Commit dc0463c

Browse files
Keep expansions and nested results across one outermost Simplify (#2015)
* Keep expansions and nested results across one outermost Simplify Simplify expanded, factored and re-simplified the same trees over and over. The polynomial pass in Simplificator.Alternate ran res.Expand() once per variable; Patterns.SumOfFractions simplified the product of the denominators afresh and the Expand and Factorize candidates were simplified again; and PolynomialFactoring.TryGetRationalCoefficients expanded once more. For t^4/((b t^2 + a + a t^2)^2 (1 + t^2)), the integrand Rubi's 4.1.7:136 hands the homogeneous substitution, 11.0 of 12.09 GB was one 342-term product expanded 72 times; 7.4.2:891 ran 2,145 factoring calls on 200 distinct polynomials and 1,232 nested Simplify calls on 214 distinct trees. The polynomial pass now expands each candidate once. Expansions and nested Simplify(expr, level) results are kept in a thread-static table that lives for the outermost Simplify only, keyed by the tree's value, each entry with its own SettingsState snapshot and used only while it still holds; only elementary trees are kept, and nothing while a RewriteRecording is open. Answers are identical. Chain against master 08a3c49: pocket of 3,836 rows (every 15th of each family, every 2nd of 4.1.7 and 7.4.2), allocation over the 3,813 solved on both 648.4 -> 375.8 GB and time 1,012 -> 815 s; samples of every family unchanged in verdicts, allocation down 19-70% per family (family 4 133.5 -> 64.7 GB); no row's allocation grew past 1.3x. Alone, 1.3.2:599 and 3.3:246 timeout -> answered, 6.6.3:213 timeout -> declined. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record SimplifyHard's lower allocation in the kernel performance baseline The gate reported IMPROVED: SimplifyHard allocates 157,895,384 B/op, 10.0% below the 175,442,312 recorded at 7c0dce12 on 2026-09-28, where the baseline was last taken. The file is the one this pull request's own Benchmark job wrote, measured-CommonFunctionsInterVersion.json from its artifact, copied over the baseline as version_performance_control.md describes. Every other gated row is within its band: SimplifyEasy +0.8%, Derivate +0.8%, SolveHard -1.5%. The run was on .NET 10.0.13 on a 4-core Ubuntu 24.04 runner, where the previous baseline records .NET 10.0.10 on an 8-core Ubuntu 26.04 machine, so the recorded times are the runner's; the gate fails on time only past 3x. The ungated map and its reasons are carried over. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f705d99 commit dc0463c

4 files changed

Lines changed: 246 additions & 43 deletions

File tree

‎Sources/AngouriMath/Functions/Algebra/Polynomials/PolynomialFactoring.cs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,10 @@ internal static bool TryGetRationalCoefficients(
323323
// https://github.com/asc-community/AngouriMath/issues/746
324324
if (!MayBeAPolynomialWithRationalCoefficients(expr, x))
325325
return false;
326+
// Through the simplification's expansions: it asks this of every candidate in every
327+
// variable, and of the same candidates again in its nested runs.
326328
var monomials = PolynomialSolver.GatherMonomialInformation<EInteger, TreeAnalyzer.PrimitiveInteger>(
327-
Sumf.LinearChildren(expr.Expand()), x);
329+
Sumf.LinearChildren(Simplificator.Expanded(expr)), x);
328330
if (monomials is null || monomials.Count < leastTerms)
329331
return false;
330332

‎Sources/AngouriMath/Functions/Simplification/Simplificator.cs‎

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,119 @@ internal static Entity PickSimplest(Entity one, Entity another)
4141

4242
/// <summary>See more details in <see cref="Entity.Simplify(int)"/></summary>
4343
internal static Entity Simplify(Entity expr, int level)
44+
{
45+
simplifying++;
46+
try
47+
{
48+
// A nested run is looked up in what the runs before it answered; the outermost
49+
// one has no runs before it. See simplifications.
50+
if (simplifying == 1 || RewriteRecording.Current is not null || !expr.Nodes.All(IsElementary))
51+
return SimplifyAtDepth(expr, level);
52+
var kept = simplifications ??= new();
53+
if (kept.TryGetValue((expr, level), out var known) && Convenience.SettingsState.StillHolds(known.Under))
54+
return known.Simplified;
55+
var under = Convenience.SettingsState.Snapshot();
56+
var simplified = SimplifyAtDepth(expr, level);
57+
if (kept.Count >= MostKept)
58+
kept.Clear();
59+
kept[(expr, level)] = (simplified, under);
60+
return simplified;
61+
}
62+
finally
63+
{
64+
if (--simplifying == 0)
65+
(expansions, simplifications) = (null, null);
66+
}
67+
}
68+
69+
/// <summary>
70+
/// How many <see cref="Simplify(Entity, int)"/> calls this thread is inside: what the
71+
/// nested runs kept is dropped when the outermost one returns.
72+
/// </summary>
73+
[ThreadStatic] private static int simplifying;
74+
75+
/// <summary>
76+
/// What the nested runs of the outermost <see cref="Simplify(Entity, int)"/> on this
77+
/// thread answered, by the tree and the level asked, each with the settings it was
78+
/// answered under.
79+
/// </summary>
80+
/// <remarks>
81+
/// A sum of two fractions simplifies its numerator and the product of its denominators,
82+
/// and a candidate's expansion and factorisation are simplified again, each a nested run
83+
/// of its own; the passes of every run meet the same sums, so the same trees are asked
84+
/// over and over. Under <c>u = sqrt((a x + 1)/(a x - 1))</c> Rubi's 7.4.2
85+
/// <c>e^(arcoth(a x)) (c - c/(a^2 x^2))^4</c> leaves a rational function whose
86+
/// simplification ran 1232 nested ones, of 214 trees. Kept on the terms
87+
/// <see cref="expansions"/> states: elementary trees only, used only under the settings
88+
/// they were answered under, and nothing where a derivation is being recorded.
89+
/// https://github.com/asc-community/AngouriMath/issues/746
90+
/// </remarks>
91+
[ThreadStatic] private static Dictionary<(Entity, int), (Entity Simplified, object?[] Under)>? simplifications;
92+
93+
/// <summary>
94+
/// The expansions the polynomial passes made inside the outermost
95+
/// <see cref="Simplify(Entity, int)"/> on this thread, by the tree expanded, each with the
96+
/// settings it was made under.
97+
/// </summary>
98+
/// <remarks>
99+
/// <para>
100+
/// <see cref="Alternate"/> expands each candidate and reads the expansion as a polynomial
101+
/// in every variable in turn, and then factors it in every variable, which expands it
102+
/// again; and nested simplifications meet the same candidate again, since a sum of
103+
/// fractions simplifies the product of its denominators afresh at each pass that meets
104+
/// it. <c>t^4/((b t^2 + a + a t^2)^2 (1 + t^2))</c>, the tangent's integrand for Rubi's
105+
/// 4.1.7 <c>sin(c + d x)^4/(a + b sin(c + d x)^2)^2</c>, expanded one product of 342
106+
/// terms 72 times, 24 trees of it three variables each, at 145 MB a time: 11 GB of
107+
/// the 12 GB its simplification cost. Under <c>u = sqrt((a x + 1)/(a x - 1))</c> Rubi's
108+
/// 7.4.2 <c>e^(arcoth(a x)) (c - c/(a^2 x^2))^4</c> asked to factor 200 polynomials in
109+
/// <c>1 - u^2</c> 2145 times, most of them past the degree the factoring takes and
110+
/// expanded to be told so: 5.4 GB.
111+
/// </para>
112+
/// <para>
113+
/// The expansion of a tree of numbers, variables, arithmetic and the elementary functions
114+
/// is a function of the tree and the settings, so one kept is the one the pass would make
115+
/// again: a tree is looked up by its value, and an expansion is used only while every
116+
/// setting reads as it did when it was made. Nothing else is kept -- a piecewise, a
117+
/// calculus operator or a statement simplifies through scopes of its own, an integral by
118+
/// integrating -- and nothing at all where a derivation is being recorded, whose steps the
119+
/// expansion writes down each time it runs.
120+
/// https://github.com/asc-community/AngouriMath/issues/746
121+
/// </para>
122+
/// </remarks>
123+
[ThreadStatic] private static Dictionary<Entity, (Entity Expanded, object?[] Under)>? expansions;
124+
125+
/// <summary>
126+
/// The most entries <see cref="expansions"/> or <see cref="simplifications"/> holds before
127+
/// it is emptied.
128+
/// </summary>
129+
private const int MostKept = 1024;
130+
131+
/// <summary>
132+
/// <paramref name="expr"/> expanded, as <see cref="Entity.Expand(int)"/> expands it, from
133+
/// <see cref="expansions"/> where it is there: inside a simplification the polynomial
134+
/// readers it asks expand through this.
135+
/// </summary>
136+
internal static Entity Expanded(Entity expr)
137+
{
138+
if (simplifying == 0 || RewriteRecording.Current is not null || !expr.Nodes.All(IsElementary))
139+
return expr.Expand();
140+
var kept = expansions ??= new();
141+
if (kept.TryGetValue(expr, out var known) && Convenience.SettingsState.StillHolds(known.Under))
142+
return known.Expanded;
143+
var under = Convenience.SettingsState.Snapshot();
144+
var expanded = expr.Expand();
145+
if (kept.Count >= MostKept)
146+
kept.Clear();
147+
kept[expr] = (expanded, under);
148+
return expanded;
149+
}
150+
151+
/// <summary>A node whose simplification reads nothing but its children and the settings.</summary>
152+
private static bool IsElementary(Entity node)
153+
=> node is Number or Variable or Sumf or Minusf or Mulf or Divf or Powf or Logf
154+
or TrigonometricFunction or Absf or Signumf;
155+
156+
private static Entity SimplifyAtDepth(Entity expr, int level)
44157
{
45158
var recording = RewriteRecording.Current;
46159
var simplified = Simplified(recording, Alternate(expr, level).First());
@@ -421,10 +534,15 @@ void __IterAddHistory(Entity expr)
421534
AddHistory(res = Simplified(recording, res.Rewrite(RewriteRules.PhiFunction)));
422535

423536
Entity? possiblePoly = null;
537+
// Expanded once for every variable rather than once a variable: the expansion
538+
// does not depend on which one it is then read in.
539+
Entity? expanded = null;
424540
foreach (var var in res.Vars)
425541
{
426542
var polyMark = recording?.Mark() ?? 0;
427-
if (TryPolynomial(res, var, out var resPoly)
543+
if ((recording is null
544+
? TryPolynomialOfExpanded(expanded ??= Expanded(res), var, out var resPoly)
545+
: TryPolynomial(res, var, out resPoly))
428546
&& (possiblePoly is null || resPoly.Complexity < possiblePoly.Complexity))
429547
AddHistory(possiblePoly = Noted(recording, res, resPoly, "AsPolynomial", polyMark));
430548
}
@@ -461,7 +579,7 @@ not solve this issue completely and yet too slow to be accepted.
461579
if (level > 0) // if level < 0 we don't check whether expanded version is better
462580
{
463581
var expandMark = recording?.Mark() ?? 0;
464-
AddHistory(Noted(recording, res, res.Expand(), nameof(Entity.Expand), expandMark).Simplify(-CandidateLevel(level)));
582+
AddHistory(Noted(recording, res, Expanded(res), nameof(Entity.Expand), expandMark).Simplify(-CandidateLevel(level)));
465583
var factorizeMark = recording?.Mark() ?? 0;
466584
// The **rule-based** factorisation, not `Entity.Factorize` -- which now also asks
467585
// the polynomial layer, and whose answers must not become candidates here. The
@@ -494,7 +612,7 @@ not solve this issue completely and yet too slow to be accepted.
494612
// Expanded, and for the same reason res is expanded above: the
495613
// cancellation only shows up once the products are multiplied out.
496614
var openedMark = recording?.Mark() ?? 0;
497-
AddHistory(Noted(recording, openedAngles, openedAngles.Expand(), nameof(Entity.Expand), openedMark).Simplify(-CandidateLevel(level)));
615+
AddHistory(Noted(recording, openedAngles, Expanded(openedAngles), nameof(Entity.Expand), openedMark).Simplify(-CandidateLevel(level)));
498616
}
499617
}
500618

@@ -508,9 +626,15 @@ not solve this issue completely and yet too slow to be accepted.
508626
internal static bool TryPolynomial(Entity expr, Variable variable,
509627
[System.Diagnostics.CodeAnalysis.NotNullWhen(true)]
510628
out Entity? dst)
629+
=> TryPolynomialOfExpanded(expr.Expand(), variable, out dst);
630+
631+
/// <summary><see cref="TryPolynomial"/> of an expression already expanded.</summary>
632+
private static bool TryPolynomialOfExpanded(Entity expanded, Variable variable,
633+
[System.Diagnostics.CodeAnalysis.NotNullWhen(true)]
634+
out Entity? dst)
511635
{
512636
dst = null;
513-
var children = Sumf.LinearChildren(expr.Expand());
637+
var children = Sumf.LinearChildren(expanded);
514638
var monomialsByPower = Algebra.AnalyticalSolving.PolynomialSolver.GatherMonomialInformation
515639
<EInteger, TreeAnalyzer.PrimitiveInteger>(children, variable);
516640
if (monomialsByPower == null)

‎Sources/Tests/DotnetBenchmark/performance-baseline.json‎

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,94 @@
11
{
22
"comment": "Allocated bytes and mean nanoseconds per operation for the popular use cases of https://github.com/asc-community/AngouriMath/issues/746. Checked by PerformanceGate.cs; see Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md for when updating it is legitimate.",
33
"benchmark": "CommonFunctionsInterVersion",
4-
"commit": "7c0dce12e6e60cb8ce33a7aae661805a9e7cab99",
5-
"measuredOn": "2026-09-28",
6-
"runtime": ".NET 10.0.10",
7-
"machine": "Ubuntu 26.04 LTS, X64, 8 logical cores",
4+
"commit": "5edd6a1f8a0e9032b314b2ceeae8e80d648e6358",
5+
"measuredOn": "2026-10-21",
6+
"runtime": ".NET 10.0.13",
7+
"machine": "Ubuntu 24.04.5 LTS, X64, 4 logical cores",
88
"cases": {
99
"CompileEasy": {
10-
"allocatedBytes": 11031,
11-
"meanNanoseconds": 187148.49052734376
10+
"allocatedBytes": 11003,
11+
"meanNanoseconds": 330806.3571777344
1212
},
1313
"CompileHard": {
14-
"allocatedBytes": 20434,
15-
"meanNanoseconds": 312400.38727678574
14+
"allocatedBytes": 20398,
15+
"meanNanoseconds": 577621.3629557291
1616
},
1717
"Derivate": {
18-
"allocatedBytes": 55783,
19-
"meanNanoseconds": 14270.340443929037
18+
"allocatedBytes": 56245,
19+
"meanNanoseconds": 27684.57133131761
2020
},
2121
"EvalEasy": {
2222
"allocatedBytes": 0,
23-
"meanNanoseconds": 2.082944576193889
23+
"meanNanoseconds": 4.0538319910948095
2424
},
2525
"EvalPolynomialFresh": {
2626
"allocatedBytes": 12536,
27-
"meanNanoseconds": 4116.869101206462
27+
"meanNanoseconds": 8084.578495279948
2828
},
2929
"EvalPolynomialFresh15Digits": {
3030
"allocatedBytes": 12064,
31-
"meanNanoseconds": 4281.885651179722
31+
"meanNanoseconds": 8616.347739083427
3232
},
3333
"EvalTranscendentalFresh": {
3434
"allocatedBytes": 34760,
35-
"meanNanoseconds": 16291.656792776925
35+
"meanNanoseconds": 30158.402158610024
3636
},
3737
"EvalTrig": {
3838
"allocatedBytes": 69944,
39-
"meanNanoseconds": 32223.94764811198
39+
"meanNanoseconds": 58949.92800467355
4040
},
4141
"EvalTrigPrecise": {
42-
"allocatedBytes": 336552,
43-
"meanNanoseconds": 410607.1845703125
42+
"allocatedBytes": 336553,
43+
"meanNanoseconds": 721994.4565104167
4444
},
4545
"ParseEasy": {
46-
"allocatedBytes": 18373,
47-
"meanNanoseconds": 5579.264629109701
46+
"allocatedBytes": 18412,
47+
"meanNanoseconds": 11023.764210292271
4848
},
4949
"ParseHard": {
50-
"allocatedBytes": 59871,
51-
"meanNanoseconds": 25569.49151611328
50+
"allocatedBytes": 60023,
51+
"meanNanoseconds": 55330.677834647046
5252
},
5353
"RunEasy": {
5454
"allocatedBytes": 0,
55-
"meanNanoseconds": 20.103014286075318
55+
"meanNanoseconds": 49.59855388317789
5656
},
5757
"RunHard": {
5858
"allocatedBytes": 0,
59-
"meanNanoseconds": 293.32832632746016
59+
"meanNanoseconds": 571.6311563650767
6060
},
6161
"RunMedium": {
6262
"allocatedBytes": 0,
63-
"meanNanoseconds": 164.38394159930093
63+
"meanNanoseconds": 322.09172677993774
6464
},
6565
"SimplifyEasy": {
66-
"allocatedBytes": 79010,
67-
"meanNanoseconds": 43888.25782877604
66+
"allocatedBytes": 79681,
67+
"meanNanoseconds": 87608.15931803385
6868
},
6969
"SimplifyHard": {
70-
"allocatedBytes": 175442312,
71-
"meanNanoseconds": 110382788.55
70+
"allocatedBytes": 157895384,
71+
"meanNanoseconds": 166394909.7647059
7272
},
7373
"SolveEasy": {
74-
"allocatedBytes": 453621,
75-
"meanNanoseconds": 205587.90686035156
74+
"allocatedBytes": 453424,
75+
"meanNanoseconds": 436456.99151141825
7676
},
7777
"SolveEasyMedium": {
78-
"allocatedBytes": 66576,
79-
"meanNanoseconds": 18447.484899902345
78+
"allocatedBytes": 66661,
79+
"meanNanoseconds": 37503.79045003255
8080
},
8181
"SolveHard": {
82-
"allocatedBytes": 10148544,
83-
"meanNanoseconds": 78453427.95959596
82+
"allocatedBytes": 9996973,
83+
"meanNanoseconds": 110732013.08888888
8484
},
8585
"SolveMedium": {
86-
"allocatedBytes": 457918,
87-
"meanNanoseconds": 272234.59720284597
86+
"allocatedBytes": 458456,
87+
"meanNanoseconds": 534248.4147385817
8888
},
8989
"SolveMediumHard": {
90-
"allocatedBytes": 1407676,
91-
"meanNanoseconds": 8650714.277083334
90+
"allocatedBytes": 1410754,
91+
"meanNanoseconds": 16481988.640625
9292
}
9393
},
9494
"ungated": {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// The rational functions a substitution leaves are simplified, and a simplification's
16+
/// nested runs expanded, factored and simplified the same trees over and over: the tangent's
17+
/// integrand for Rubi's 4.1.7 <c>sin(c + d x)^4/(a + b sin(c + d x)^2)^2</c> cost 12 GB, 11 GB
18+
/// of it one product of 342 terms expanded 72 times. Each is now done once a simplification.
19+
/// The answers are the ones made afresh gave; the symbols are this test's own, so that no
20+
/// answer the integrator remembers from another test is what is measured.
21+
/// <a href="https://github.com/asc-community/AngouriMath/issues/746">#746</a>
22+
/// </summary>
23+
[Trait("Area", "Calculus")]
24+
public sealed class ExpansionsKeptAcrossASimplificationIntegralTest
25+
{
26+
/// <summary>
27+
/// Made afresh, the four cost 12.1, 8.8, 7.8 and 21.6 GB; kept, 0.4, 0.5, 0.8 and 2.6. The
28+
/// last is mostly polynomials in <c>1 - u^2</c> factored past the degree the factoring
29+
/// takes, and the simplifications of the sums they came from.
30+
/// </summary>
31+
[Theory]
32+
[InlineData("sin(kc + kd*x)^4/(ka + kb*sin(kc + kd*x)^2)^2", 3_000_000_000L, -1.2, -0.7, 0.3, 0.8, 1.3, 2.1)]
33+
[InlineData("csc(kc + kd*x)^4/(ka + kb*sin(kc + kd*x)^2)^2", 3_000_000_000L, -1.2, -0.7, 0.3, 0.8, 1.3, 2.1)]
34+
[InlineData("e^acoth(ka*x)/(kc - kc/(ka*x))^4", 3_000_000_000L, 0.9, 1.3, 1.7, 2.2, 2.9, 3.6)]
35+
[InlineData("e^acoth(ka*x)*(kc - kc/(ka^2*x^2))^4", 8_000_000_000L, 0.9, 1.3, 1.7, 2.2, 2.9, 3.6)]
36+
public void AnsweredAsBeforeForAFractionOfTheAllocation(string integrand, long bytes, double p1, double p2, double p3, double p4, double p5, double p6)
37+
{
38+
var before = GC.GetAllocatedBytesForCurrentThread();
39+
var integral = integrand.ToEntity().Integrate("x");
40+
var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
41+
var text = integral.Stringize();
42+
Assert.DoesNotContain("integral(", text);
43+
Assert.True(allocated < bytes, $"{allocated / 1e9:F2} GB to integrate {integrand}, over the {bytes / 1e9:F0} GB bound");
44+
Entity Pinned(Entity e) => e.Substitute("ka", 1.3).Substitute("kb", 0.7).Substitute("kc", 0.4).Substitute("kd", 0.3);
45+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
46+
var original = Pinned(integrand.ToEntity());
47+
var compared = 0;
48+
foreach (var at in new[] { p1, p2, p3, p4, p5, p6 })
49+
{
50+
var want = original.Substitute("x", at).EvalNumerical();
51+
var got = derivative.Substitute("x", at).EvalNumerical();
52+
if (want.IsNaN)
53+
continue;
54+
compared++;
55+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
56+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
57+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
58+
}
59+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
60+
}
61+
62+
/// <summary>
63+
/// The simplification itself, of that integrand as the substitution writes it: the form it
64+
/// always had, for a thirtieth of the 12 GB it cost.
65+
/// </summary>
66+
[Fact]
67+
public void TheTangentsIntegrandSimplifiesAsBefore()
68+
{
69+
var written = "(wt*1)^4/((wb*(wt*1)^2 + wa*1^2 + wa*(wt*1)^2)^2*(1 + wt^2))".ToEntity();
70+
var before = GC.GetAllocatedBytesForCurrentThread();
71+
var simplified = written.Simplify();
72+
var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
73+
Assert.Equal("wt ^ 4 / ((wa * (1 + wt ^ 2) + wb * wt ^ 2) ^ 2 * (wt ^ 2 + 1))", simplified.Stringize());
74+
Assert.True(allocated < 3_000_000_000L, $"{allocated / 1e9:F2} GB to simplify {written}");
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)