Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Use Span.CopyTo(Span) in BigInteger's add and subtract by speshuric · Pull Request #83951 · dotnet/runtime · GitHub
Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Use Span.CopyTo(Span) in BigInteger's add and subtract by speshuric · Pull Request #83951 · dotnet/runtime · GitHub
Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Use Span.CopyTo(Span) in BigInteger's add and subtract by speshuric · Pull Request #83951 · dotnet/runtime · GitHub
Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Use Span.CopyTo(Span) in BigInteger's add and subtract by speshuric · Pull Request #83951 · dotnet/runtime · GitHub
Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Use Span.CopyTo(Span) in BigInteger's add and subtract by speshuric · Pull Request #83951 · dotnet/runtime · GitHub
Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Use Span.CopyTo(Span) in BigInteger's add and subtract by speshuric · Pull Request #83951 · dotnet/runtime · GitHub
Skip to content

Use Span.CopyTo(Span) in BigInteger's add and subtract - #83951

Merged
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub
Jul 18, 2023
Merged

Use Span.CopyTo(Span) in BigInteger's add and subtract#83951
adamsitnik merged 5 commits into
dotnet:mainfrom
speshuric:bigintaddsub

Conversation

@speshuric

Copy link
Copy Markdown
Contributor

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.

See #83457 for details.

Do not merge until discussed.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Numerics labels Mar 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

See #83457 for details.

Do not merge until discussed.

Author:speshuric
Assignees:-
Labels:

area-System.Numerics, community-contribution

Milestone:-

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 #83457 for details.
@adamsitnikadamsitnik self-assigned this Jul 18, 2023
@adamsitnikadamsitnik added the tenet-performance Performance related issue label Jul 18, 2023

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've synced your fork locally with main (to include most recent JIT changes) and compared it against main. The performance gains are impressive, great job @speshuric !

Details
MethodJobargumentsMeanRatioAllocatedAlloc Ratio
AddPR128,128 bits20.45 ns0.9748 B1.00
Addmain128,128 bits21.02 ns1.0048 B1.00
AddPR128,2048 bits57.58 ns0.68280 B1.00
Addmain128,2048 bits84.75 ns1.00280 B1.00
AddPR128,32 bits19.67 ns0.9440 B1.00
Addmain128,32 bits20.98 ns1.0040 B1.00
AddPR128,512 bits25.22 ns0.9288 B1.00
Addmain128,512 bits27.46 ns1.0088 B1.00
AddPR128,8192 bits89.94 ns0.441048 B1.00
Addmain128,8192 bits206.56 ns1.001048 B1.00
AddPR131072,128 bits827.94 ns0.3016408 B1.00
Addmain131072,128 bits2,781.52 ns1.0016408 B1.00
AddPR131072,2048 bits927.65 ns0.3316408 B1.00
Addmain131072,2048 bits2,994.30 ns1.0016408 B1.00
AddPR131072,32 bits856.88 ns0.2916408 B1.00
Addmain131072,32 bits2,928.15 ns1.0016408 B1.00
AddPR131072,512 bits932.48 ns0.3116408 B1.00
Addmain131072,512 bits3,017.33 ns1.0016408 B1.00
AddPR131072,8192 bits1,051.76 ns0.3516408 B1.00
Addmain131072,8192 bits2,981.83 ns1.0016408 B1.00
AddPR2048,128 bits50.38 ns0.61280 B1.00
Addmain2048,128 bits81.90 ns1.00280 B1.00
AddPR2048,2048 bits88.24 ns0.99288 B1.00
Addmain2048,2048 bits89.13 ns1.00288 B1.00
AddPR2048,32 bits52.21 ns0.65280 B1.00
Addmain2048,32 bits80.10 ns1.00280 B1.00
AddPR2048,512 bits57.34 ns0.69280 B1.00
Addmain2048,512 bits82.59 ns1.00280 B1.00
AddPR2048,8192 bits125.71 ns0.551048 B1.00
Addmain2048,8192 bits229.65 ns1.001048 B1.00
AddPR2097152,128 bits22,540.72 ns0.37262204 B1.00
Addmain2097152,128 bits60,474.17 ns1.00262214 B1.00
AddPR2097152,2048 bits23,170.94 ns0.41262203 B1.00
Addmain2097152,2048 bits56,260.18 ns1.00262214 B1.00
AddPR2097152,32 bits27,136.58 ns0.42262206 B1.00
Addmain2097152,32 bits64,525.60 ns1.00262215 B1.00
AddPR2097152,512 bits22,966.01 ns0.40262203 B1.00
Addmain2097152,512 bits56,140.32 ns1.00262214 B1.00
AddPR2097152,8192 bits23,176.13 ns0.40262203 B1.00
Addmain2097152,8192 bits56,357.32 ns1.00262214 B1.00
AddPR32,128 bits19.22 ns0.9540 B1.00
Addmain32,128 bits20.18 ns1.0040 B1.00
AddPR32,2048 bits51.55 ns0.63280 B1.00
Addmain32,2048 bits81.77 ns1.00280 B1.00
AddPR32,32 bits17.60 ns0.9932 B1.00
Addmain32,32 bits17.69 ns1.0032 B1.00
AddPR32,512 bits23.69 ns0.8688 B1.00
Addmain32,512 bits27.59 ns1.0088 B1.00
AddPR32,8192 bits86.92 ns0.391048 B1.00
Addmain32,8192 bits222.72 ns1.001048 B1.00
AddPR32768,128 bits224.63 ns0.324120 B1.00
Addmain32768,128 bits698.71 ns1.004120 B1.00
AddPR32768,2048 bits272.23 ns0.384120 B1.00
Addmain32768,2048 bits721.08 ns1.004120 B1.00
AddPR32768,32 bits226.28 ns0.294120 B1.00
Addmain32768,32 bits771.12 ns1.004120 B1.00
AddPR32768,512 bits231.20 ns0.334120 B1.00
Addmain32768,512 bits704.56 ns1.004120 B1.00
AddPR32768,8192 bits412.62 ns0.554120 B1.00
Addmain32768,8192 bits754.78 ns1.004120 B1.00
AddPR512,128 bits24.13 ns0.8788 B1.00
Addmain512,128 bits27.55 ns1.0088 B1.00
AddPR512,2048 bits59.59 ns0.68280 B1.00
Addmain512,2048 bits87.00 ns1.00280 B1.00
AddPR512,32 bits22.30 ns0.8388 B1.00
Addmain512,32 bits26.89 ns1.0088 B1.00
AddPR512,512 bits26.70 ns0.9388 B1.00
Addmain512,512 bits28.60 ns1.0088 B1.00
AddPR512,8192 bits93.30 ns0.411048 B1.00
Addmain512,8192 bits227.73 ns1.001048 B1.00
AddPR524288,128 bits3,546.73 ns0.3265560 B1.00
Addmain524288,128 bits10,926.60 ns1.0065560 B1.00
AddPR524288,2048 bits3,479.27 ns0.3165560 B1.00
Addmain524288,2048 bits11,146.39 ns1.0065560 B1.00
AddPR524288,32 bits3,484.67 ns0.3265560 B1.00
Addmain524288,32 bits10,955.92 ns1.0065560 B1.00
AddPR524288,512 bits3,546.81 ns0.3265560 B1.00
Addmain524288,512 bits11,024.72 ns1.0065560 B1.00
AddPR524288,8192 bits3,615.26 ns0.3365560 B1.00
Addmain524288,8192 bits11,059.42 ns1.0065560 B1.00
AddPR8192,128 bits84.49 ns0.411048 B1.00
Addmain8192,128 bits205.40 ns1.001048 B1.00
AddPR8192,2048 bits128.64 ns0.551048 B1.00
Addmain8192,2048 bits233.31 ns1.001048 B1.00
AddPR8192,32 bits85.97 ns0.431048 B1.00
Addmain8192,32 bits200.82 ns1.001048 B1.00
AddPR8192,512 bits92.90 ns0.411048 B1.00
Addmain8192,512 bits226.46 ns1.001048 B1.00
AddPR8192,8192 bits267.35 ns1.041048 B1.00
Addmain8192,8192 bits258.14 ns1.001048 B1.00

I've left some suggestions. Since this PR was stale for more than a quarter (it's our fault) and we snap for .NET 8 Preview 7 today, I am going to try to apply my suggestions myself and push the changes if the perf does not get worse. If I can't address my suggestions without regressing perf, I am going to merge as is.

Comment on lines +31 to +32
// Switching to managed references helps eliminating
// index bounds check...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the code was eliminating index bound checks for left[i], as it was always used within a loop that was recognized by the JIT:

for(inti=0;i<left.Length;i++)longdigit=left[i]+carry;

This was not true for bits, because JIT did not know that bits.Length == left.Length + 1.

We could avoid using managed references for left and just keep using the old loop here and access bits via Unsafe.Add(ref managedRefToBits).

Suggested change
// Switching to managed references helps eliminating
// index bounds check...
// Switching to managed references helps eliminating
// index bounds check for both buffers.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code seems identical with part of Add(ReadOnlySpan<uint> left, uint right, Span<uint> bits). If possible we should refactor it to helper method and enforce inlining, so the code is reused but there is no perf penalty.

long digit = left[i] + carry;
Unsafe.Add(ref resultPtr, i) = (uint)digit;
carry = digit >> 32;
for ( ; carry != 0 && i < upperBound; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason why in other loops you have added a break for carry == 0 while here you moved the condition here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was some differences in asm which is significant for "worst cases", but I need to redeploy this test environment to show.

@speshuric

Copy link
Copy Markdown
ContributorAuthor

Since this PR was stale for more than a quarter

Sorry, I had to switch to some other activities on main job. But it seems now I can back to this.

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @speshuric once again thank you for your contribution and apologies for the delay in review process!

@adamsitnik
adamsitnik merged commit 2f843a8 into dotnet:mainJul 18, 2023
@speshuric
speshuric deleted the bigintaddsub branch July 19, 2023 00:54
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numericscommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@speshuric@adamsitnik