Skip to content

BigInteger parsing optimizations - #47842

Merged
jeffhandley merged 4 commits into
dotnet:mainfrom
jfd16:bigint-parse-perf
May 9, 2021
Merged

BigInteger parsing optimizations#47842
jeffhandley merged 4 commits into
dotnet:mainfrom
jfd16:bigint-parse-perf

Conversation

@jfd16

@jfd16jfd16 commented Feb 4, 2021

Copy link
Copy Markdown
Contributor

Improves performance of BigInteger parsing from decimal and hex strings (both speed and memory allocations), according to the benchmark that I have included. The difference is particularly significant for large decimal strings, given that the current implementation allocates two BigInteger instances for each input digit.

Benchmark code

usingSystem.Collections.Generic;usingSystem.Globalization;usingSystem.Linq;usingBenchmarkDotNet.Attributes;publicclassBigIntegerParseBenchmarks{publicIEnumerable<object>NumberStrings=>newstring[]{"123",int.MinValue.ToString(),string.Concat(Enumerable.Repeat("1234567890",20)),string.Concat(Enumerable.Repeat("654719003",57)),};publicIEnumerable<object>NumberStringsHex=>newstring[]{"12A",int.MinValue.ToString("X"),string.Concat(Enumerable.Repeat("1234567890abcdefffff",10)),string.Concat(Enumerable.Repeat("18a473f5d",57)),};[Benchmark][ArgumentsSource(nameof(NumberStrings))]publicBigIntegerParse(stringnumberString)=>BigInteger.Parse(numberString);[Benchmark][ArgumentsSource(nameof(NumberStringsHex))]publicBigIntegerParseHex(stringnumberString)=>BigInteger.Parse(numberString,NumberStyles.HexNumber);}

Benchmark results (on master branch)

BenchmarkDotNet=v0.12.1.1466-nightly, OS=Windows 10.0.18363.1316 (1909/November2019Update/19H2)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.102
[Host] : .NET 6.0.0 (6.0.21.10203), X64 RyuJIT
Job-NSGODO : .NET 6.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable Toolchain=CoreRun IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodnumberStringMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Parse123186.5 ns3.43 ns2.87 ns187.0 ns181.9 ns192.6 ns0.0246--104 B
Parse-2147483648418.0 ns16.69 ns18.55 ns419.0 ns359.6 ns442.6 ns0.0312--136 B
Parse123456789012(...)901234567890 [200]17,866.9 ns193.43 ns180.93 ns17,901.3 ns17,527.7 ns18,163.3 ns13.1653--55,176 B
Parse654719003654(...)003654719003 [513]69,284.7 ns643.04 ns536.97 ns69,112.3 ns68,447.0 ns70,545.0 ns65.5556--274,272 B
ParseHex12A121.8 ns0.64 ns0.50 ns121.7 ns121.3 ns123.2 ns0.0323--136 B
ParseHex8000000183.5 ns1.28 ns1.07 ns183.6 ns182.4 ns185.5 ns0.0319--136 B
ParseHex1234567890ab(...)90abcdefffff [200]2,520.9 ns17.84 ns14.90 ns2,511.0 ns2,505.7 ns2,544.7 ns0.2609--1,128 B
ParseHex18a473f5d18a(...)f5d18a473f5d [513]6,069.5 ns83.14 ns77.77 ns6,045.6 ns5,938.0 ns6,202.7 ns0.7287--3,128 B

Results after changes

BenchmarkDotNet=v0.12.1.1466-nightly, OS=Windows 10.0.18363.1316 (1909/November2019Update/19H2)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.102
[Host] : .NET 6.0.0 (6.0.21.10203), X64 RyuJIT
Job-WMUKDU : .NET 6.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable Toolchain=CoreRun IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodnumberStringMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Parse123126.0 ns1.81 ns1.51 ns125.8 ns123.7 ns128.9 ns0.0248--104 B
Parse-2147483648176.5 ns3.55 ns3.65 ns174.7 ns171.9 ns182.6 ns0.0322--136 B
Parse123456789012(...)901234567890 [200]1,843.5 ns21.83 ns20.42 ns1,843.6 ns1,819.9 ns1,875.5 ns0.2339--984 B
Parse654719003654(...)003654719003 [513]5,197.3 ns23.14 ns20.51 ns5,206.4 ns5,134.6 ns5,210.8 ns0.6656--2,792 B
ParseHex12A116.3 ns1.23 ns1.09 ns116.2 ns114.9 ns118.8 ns0.0245--104 B
ParseHex8000000166.1 ns1.76 ns1.56 ns165.9 ns164.2 ns169.3 ns0.0320--136 B
ParseHex1234567890ab(...)90abcdefffff [200]1,705.1 ns10.51 ns8.77 ns1,703.0 ns1,692.7 ns1,715.7 ns0.2362--1,000 B
ParseHex18a473f5d18a(...)f5d18a473f5d [513]3,905.5 ns34.60 ns30.67 ns3,907.6 ns3,866.8 ns3,975.6 ns0.6660--2,840 B

@ghost

ghost commented Feb 4, 2021

Copy link
Copy Markdown

Tagging subscribers to this area: @tannergooding, @pgovind
See info in area-owners.md if you want to be subscribed.

Issue Details

Improves performance of BigInteger parsing from decimal and hex strings (both speed and memory allocations), according to the benchmark that I have included. The difference is particularly significant for large decimal strings, given that the current implementation allocates two BigInteger instances for each input digit.

Benchmark code

usingSystem.Collections.Generic;usingSystem.Globalization;usingSystem.Linq;usingBenchmarkDotNet.Attributes;publicclassBigIntegerParseBenchmarks{publicIEnumerable<object>NumberStrings=>newstring[]{"123",int.MinValue.ToString(),string.Concat(Enumerable.Repeat("1234567890",20)),string.Concat(Enumerable.Repeat("654719003",57)),};publicIEnumerable<object>NumberStringsHex=>newstring[]{"12A",int.MinValue.ToString("X"),string.Concat(Enumerable.Repeat("1234567890abcdefffff",10)),string.Concat(Enumerable.Repeat("18a473f5d",57)),};[Benchmark][ArgumentsSource(nameof(NumberStrings))]publicBigIntegerParse(stringnumberString)=>BigInteger.Parse(numberString);[Benchmark][ArgumentsSource(nameof(NumberStringsHex))]publicBigIntegerParseHex(stringnumberString)=>BigInteger.Parse(numberString,NumberStyles.HexNumber);}

Benchmark results (on master branch)

BenchmarkDotNet=v0.12.1.1466-nightly, OS=Windows 10.0.18363.1316 (1909/November2019Update/19H2)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.102
[Host] : .NET 6.0.0 (6.0.21.10203), X64 RyuJIT
Job-NSGODO : .NET 6.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable Toolchain=CoreRun IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodnumberStringMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Parse123186.5 ns3.43 ns2.87 ns187.0 ns181.9 ns192.6 ns0.0246--104 B
Parse-2147483648418.0 ns16.69 ns18.55 ns419.0 ns359.6 ns442.6 ns0.0312--136 B
Parse123456789012(...)901234567890 [200]17,866.9 ns193.43 ns180.93 ns17,901.3 ns17,527.7 ns18,163.3 ns13.1653--55,176 B
Parse654719003654(...)003654719003 [513]69,284.7 ns643.04 ns536.97 ns69,112.3 ns68,447.0 ns70,545.0 ns65.5556--274,272 B
ParseHex12A121.8 ns0.64 ns0.50 ns121.7 ns121.3 ns123.2 ns0.0323--136 B
ParseHex8000000183.5 ns1.28 ns1.07 ns183.6 ns182.4 ns185.5 ns0.0319--136 B
ParseHex1234567890ab(...)90abcdefffff [200]2,520.9 ns17.84 ns14.90 ns2,511.0 ns2,505.7 ns2,544.7 ns0.2609--1,128 B
ParseHex18a473f5d18a(...)f5d18a473f5d [513]6,069.5 ns83.14 ns77.77 ns6,045.6 ns5,938.0 ns6,202.7 ns0.7287--3,128 B

Results after changes

BenchmarkDotNet=v0.12.1.1466-nightly, OS=Windows 10.0.18363.1316 (1909/November2019Update/19H2)
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.102
[Host] : .NET 6.0.0 (6.0.21.10203), X64 RyuJIT
Job-WMUKDU : .NET 6.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable Toolchain=CoreRun IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodnumberStringMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Parse123126.0 ns1.81 ns1.51 ns125.8 ns123.7 ns128.9 ns0.0248--104 B
Parse-2147483648176.5 ns3.55 ns3.65 ns174.7 ns171.9 ns182.6 ns0.0322--136 B
Parse123456789012(...)901234567890 [200]1,843.5 ns21.83 ns20.42 ns1,843.6 ns1,819.9 ns1,875.5 ns0.2339--984 B
Parse654719003654(...)003654719003 [513]5,197.3 ns23.14 ns20.51 ns5,206.4 ns5,134.6 ns5,210.8 ns0.6656--2,792 B
ParseHex12A116.3 ns1.23 ns1.09 ns116.2 ns114.9 ns118.8 ns0.0245--104 B
ParseHex8000000166.1 ns1.76 ns1.56 ns165.9 ns164.2 ns169.3 ns0.0320--136 B
ParseHex1234567890ab(...)90abcdefffff [200]1,705.1 ns10.51 ns8.77 ns1,703.0 ns1,692.7 ns1,715.7 ns0.2362--1,000 B
ParseHex18a473f5d18a(...)f5d18a473f5d [513]3,905.5 ns34.60 ns30.67 ns3,907.6 ns3,866.8 ns3,975.6 ns0.6660--2,840 B
Author:jfd16
Assignees:-
Labels:

area-System.Numerics

Milestone:-

@dnfadmin

dnfadmin commented Feb 4, 2021

Copy link
Copy Markdown

CLA assistant check
All CLA requirements met.

Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
@danmoseley

Copy link
Copy Markdown
Contributor

The use of ArrayPool is much more common than ArrayPool

Grepped under libraries/*/src/**cs for interest and I see

object - 2
IntPtr, GCHandle - 4 each
char - 118
byte - 301

there are no uses I see for int or uint.

@tannergooding

Copy link
Copy Markdown
Member

Grepped under libraries/*/src/**cs for interest and I see

I wonder if there is something we can do here to promote better sharing. Particularly when using Span the underlying data type doesn't really matter too much (provided it has the correct alignment)

@jfd16

jfd16 commented Feb 5, 2021

Copy link
Copy Markdown
ContributorAuthor

The use of ArrayPool is much more common than ArrayPool

Grepped under libraries/*/src/**cs for interest and I see

object - 2
IntPtr, GCHandle - 4 each
char - 118
byte - 301

there are no uses I see for int or uint.

ArrayPool<int> is being used indirectly in some places through ValueListBuilder: https://github.com/dotnet/runtime/search?q=ValueListBuilder+path%3Asrc

Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs Outdated
@jfd16
jfd16force-pushed the bigint-parse-perf branch 3 times, most recently from 3041198 to 2383bd3CompareFebruary 9, 2021 01:42
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated

try
{
char c = number.digits[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.

@stephentoub, is this an opportunity for an analyzer? Indexing into StringBuilder can be deceptively slow due to its internal chunking.

isNegative = true;
foreach (ReadOnlyMemory<char> digitsChunkMem in number.digits.GetChunks())
{
ReadOnlySpan<char> chunkDigits = digitsChunkMem.Span;

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.

Likewise, I wonder if we should have a (internal for now) CurrentSpan property that is slightly more efficient.

We don't really need to return a ReadOnlyMemory when we only want to extract the underlying Span<T>, particularly when the ROM<T> is just being constructed over a backing array.

It's, in practice, no more unsafe than say CollectionsMarshal.AsSpan

@tannergoodingtannergooding 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. I think it can be merged provided @stephentoub has no additional feedback

@danmoseley

Copy link
Copy Markdown
Contributor

I wonder if there is something we can do here to promote better sharing. Particularly when using Span the underlying data type doesn't really matter too much (provided it has the correct alignment

Worth an issue do you think @tannergooding

Base automatically changed from master to mainMarch 1, 2021 09:07
@jeffhandley

Copy link
Copy Markdown
Member

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jeffhandley
jeffhandley merged commit b3278ca into dotnet:mainMay 9, 2021
@karelzkarelz added this to the 6.0.0 milestone May 20, 2021
@jfd16
jfd16 deleted the bigint-parse-perf branch May 21, 2021 04:16
@jfd16
jfd16 restored the bigint-parse-perf branch June 16, 2021 02:12
@ghostghost locked as resolved and limited conversation to collaborators Jul 16, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@jfd16@dnfadmin@danmoseley@tannergooding@jeffhandley@stephentoub@karelz