Skip to content

BigInteger parsing optimization for large decimal string - #51953

Closed
key-moon wants to merge 3 commits into
dotnet:mainfrom
key-moon:bigint-parse-divide-conquer
Closed

BigInteger parsing optimization for large decimal string#51953
key-moon wants to merge 3 commits into
dotnet:mainfrom
key-moon:bigint-parse-divide-conquer

Conversation

@key-moon

@key-moonkey-moon commented Apr 27, 2021

Copy link
Copy Markdown
Contributor

Current BigNumer.NumberToBigInteger method is implemented using naive algorithm. It runs in Θ(N^2) time where N is number of digits. I implemented faster method known as divide-and-conquer algorithm. It runs Θ(N (log(N))^2). Since this algorithms running time has large constant factor, naive method is faster when N is small. So This method is only apply when N is large enough. (specifically, use divide-and-conquer method when N is more than 20000.)

I created branch from #47842 as it looks like #47842 will be merged shortly.

benchmark result

Previous method
BenchmarkDotNet=v0.12.1.1528-nightly, OS=Windows 10.0.19042.928 (20H2/October2020Update)
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET 6.0.0 (6.0.21.20104), X64 RyuJIT
Job-CHCQPG : .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
Parse12345678901(...)01234567890 [20000]2.611 ms0.1786 ms0.2057 ms2.661 ms2.208 ms2.996 ms26.7857--56 KB
Parse12345678901(...)01234567890 [40000]9.028 ms0.7436 ms0.8265 ms8.816 ms8.072 ms10.909 ms31.2500--96 KB
Parse12345678901(...)01234567890 [60000]18.521 ms0.5179 ms0.5756 ms18.552 ms17.349 ms19.541 ms71.4286--151 KB
Parse12345678901(...)01234567890 [80000]32.904 ms1.1486 ms1.2290 ms32.761 ms30.984 ms34.686 ms---191 KB
Parse12345678901(...)01234567890 [100000]51.271 ms1.7700 ms2.0384 ms51.186 ms48.447 ms54.856 ms---246 KB
Parse12345678901(...)01234567890 [120000]74.285 ms3.6392 ms3.8939 ms73.489 ms68.604 ms82.719 ms---286 KB
Parse12345678901(...)01234567890 [140000]100.248 ms5.2825 ms5.8715 ms98.712 ms92.597 ms113.046 ms---341 KB
Parse12345678901(...)01234567890 [160000]127.443 ms4.8226 ms5.5537 ms125.749 ms120.504 ms138.583 ms---381 KB
Parse12345678901(...)01234567890 [180000]162.028 ms4.9852 ms5.7409 ms162.142 ms151.841 ms171.640 ms---436 KB
Parse12345678901(...)01234567890 [200000]224.511 ms16.7864 ms19.3313 ms224.392 ms192.558 ms255.098 ms---476 KB
Parse12345678901(...)01234567890 [220000]240.451 ms10.9087 ms12.1250 ms234.971 ms226.956 ms265.072 ms---531 KB
Parse12345678901(...)01234567890 [240000]300.378 ms25.1560 ms27.9608 ms296.348 ms267.565 ms357.131 ms---572 KB
Parse12345678901(...)01234567890 [260000]346.397 ms26.8156 ms29.8054 ms335.024 ms320.430 ms412.120 ms---626 KB
Parse12345678901(...)01234567890 [280000]384.442 ms12.8365 ms14.2678 ms381.489 ms367.624 ms420.767 ms---666 KB
Parse12345678901(...)01234567890 [300000]433.914 ms9.1847 ms10.5772 ms433.046 ms414.254 ms452.394 ms---721 KB
Implemented method
BenchmarkDotNet=v0.12.1.1528-nightly, OS=Windows 10.0.19042.928 (20H2/October2020Update)
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET 6.0.0 (6.0.21.20104), X64 RyuJIT
Job-YASIZJ : .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
Parse12345678901(...)01234567890 [20000]2.017 ms0.0559 ms0.0643 ms2.024 ms1.921 ms2.127 ms140.6250--288 KB
Parse12345678901(...)01234567890 [40000]6.237 ms0.1116 ms0.0932 ms6.218 ms6.127 ms6.434 ms437.500083.3333-994 KB
Parse12345678901(...)01234567890 [60000]15.522 ms1.3853 ms1.5953 ms15.153 ms13.686 ms18.949 ms1062.5000218.7500-2,831 KB
Parse12345678901(...)01234567890 [80000]20.554 ms0.4685 ms0.5208 ms20.429 ms19.880 ms21.552 ms1181.8182181.8182-3,572 KB
Parse12345678901(...)01234567890 [100000]39.146 ms1.8050 ms1.8536 ms38.681 ms36.992 ms44.189 ms3000.0000333.3333-7,680 KB
Parse12345678901(...)01234567890 [120000]47.999 ms1.5866 ms1.8271 ms47.429 ms46.147 ms51.705 ms3750.0000250.0000-10,447 KB
Parse12345678901(...)01234567890 [140000]41.915 ms1.3341 ms1.3700 ms41.657 ms40.299 ms44.621 ms2166.6667500.0000-6,532 KB
Parse12345678901(...)01234567890 [160000]67.668 ms1.4404 ms1.6010 ms67.296 ms65.587 ms70.763 ms4750.0000250.0000-12,897 KB
Parse12345678901(...)01234567890 [180000]103.943 ms2.4616 ms2.8347 ms103.285 ms100.249 ms109.843 ms6500.0000500.0000-18,962 KB
Parse12345678901(...)01234567890 [200000]136.343 ms3.9093 ms4.0146 ms135.607 ms131.575 ms148.198 ms9500.0000500.0000-28,922 KB
Parse12345678901(...)01234567890 [220000]154.695 ms3.6123 ms3.7096 ms154.182 ms149.801 ms163.375 ms17500.0000500.0000-40,999 KB
Parse12345678901(...)01234567890 [240000]184.792 ms20.8938 ms24.0613 ms173.449 ms163.513 ms234.269 ms16000.00001000.0000-39,244 KB
Parse12345678901(...)01234567890 [260000]160.434 ms3.0439 ms3.5053 ms160.026 ms155.602 ms167.424 ms12500.0000500.0000-33,322 KB
Parse12345678901(...)01234567890 [280000]134.838 ms2.4207 ms2.2644 ms134.531 ms132.314 ms139.587 ms9500.0000500.0000-22,696 KB
Parse12345678901(...)01234567890 [300000]140.690 ms2.4686 ms2.4245 ms141.285 ms134.758 ms144.021 ms7500.00001500.00001000.000024,318 KB

@ghost

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

Current BigNumer.NumberToBigInteger method is implemented using naive algorithm. It runs in Θ(N^2) time where N is number of digits. I implemented faster method known as divide-and-conquer algorithm. It runs Θ(N (log(N))^2). Since this algorithms running time has large constant factor, naive method is faster when N is small. So This method is only apply when N is large enough. (specifically, use divide-and-conquer method when N is more than 20000.)

I created branch from #47842 because #47872 looks like merged soon. sorry for inconvenience.

benchmark result

Previous method
BenchmarkDotNet=v0.12.1.1528-nightly, OS=Windows 10.0.19042.928 (20H2/October2020Update)
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET 6.0.0 (6.0.21.20104), X64 RyuJIT
Job-CHCQPG : .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
Parse12345678901(...)01234567890 [20000]2.611 ms0.1786 ms0.2057 ms2.661 ms2.208 ms2.996 ms26.7857--56 KB
Parse12345678901(...)01234567890 [40000]9.028 ms0.7436 ms0.8265 ms8.816 ms8.072 ms10.909 ms31.2500--96 KB
Parse12345678901(...)01234567890 [60000]18.521 ms0.5179 ms0.5756 ms18.552 ms17.349 ms19.541 ms71.4286--151 KB
Parse12345678901(...)01234567890 [80000]32.904 ms1.1486 ms1.2290 ms32.761 ms30.984 ms34.686 ms---191 KB
Parse12345678901(...)01234567890 [100000]51.271 ms1.7700 ms2.0384 ms51.186 ms48.447 ms54.856 ms---246 KB
Parse12345678901(...)01234567890 [120000]74.285 ms3.6392 ms3.8939 ms73.489 ms68.604 ms82.719 ms---286 KB
Parse12345678901(...)01234567890 [140000]100.248 ms5.2825 ms5.8715 ms98.712 ms92.597 ms113.046 ms---341 KB
Parse12345678901(...)01234567890 [160000]127.443 ms4.8226 ms5.5537 ms125.749 ms120.504 ms138.583 ms---381 KB
Parse12345678901(...)01234567890 [180000]162.028 ms4.9852 ms5.7409 ms162.142 ms151.841 ms171.640 ms---436 KB
Parse12345678901(...)01234567890 [200000]224.511 ms16.7864 ms19.3313 ms224.392 ms192.558 ms255.098 ms---476 KB
Parse12345678901(...)01234567890 [220000]240.451 ms10.9087 ms12.1250 ms234.971 ms226.956 ms265.072 ms---531 KB
Parse12345678901(...)01234567890 [240000]300.378 ms25.1560 ms27.9608 ms296.348 ms267.565 ms357.131 ms---572 KB
Parse12345678901(...)01234567890 [260000]346.397 ms26.8156 ms29.8054 ms335.024 ms320.430 ms412.120 ms---626 KB
Parse12345678901(...)01234567890 [280000]384.442 ms12.8365 ms14.2678 ms381.489 ms367.624 ms420.767 ms---666 KB
Parse12345678901(...)01234567890 [300000]433.914 ms9.1847 ms10.5772 ms433.046 ms414.254 ms452.394 ms---721 KB
Implemented method
BenchmarkDotNet=v0.12.1.1528-nightly, OS=Windows 10.0.19042.928 (20H2/October2020Update)
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=6.0.100-preview.3.21202.5
[Host] : .NET 6.0.0 (6.0.21.20104), X64 RyuJIT
Job-YASIZJ : .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
Parse12345678901(...)01234567890 [20000]2.017 ms0.0559 ms0.0643 ms2.024 ms1.921 ms2.127 ms140.6250--288 KB
Parse12345678901(...)01234567890 [40000]6.237 ms0.1116 ms0.0932 ms6.218 ms6.127 ms6.434 ms437.500083.3333-994 KB
Parse12345678901(...)01234567890 [60000]15.522 ms1.3853 ms1.5953 ms15.153 ms13.686 ms18.949 ms1062.5000218.7500-2,831 KB
Parse12345678901(...)01234567890 [80000]20.554 ms0.4685 ms0.5208 ms20.429 ms19.880 ms21.552 ms1181.8182181.8182-3,572 KB
Parse12345678901(...)01234567890 [100000]39.146 ms1.8050 ms1.8536 ms38.681 ms36.992 ms44.189 ms3000.0000333.3333-7,680 KB
Parse12345678901(...)01234567890 [120000]47.999 ms1.5866 ms1.8271 ms47.429 ms46.147 ms51.705 ms3750.0000250.0000-10,447 KB
Parse12345678901(...)01234567890 [140000]41.915 ms1.3341 ms1.3700 ms41.657 ms40.299 ms44.621 ms2166.6667500.0000-6,532 KB
Parse12345678901(...)01234567890 [160000]67.668 ms1.4404 ms1.6010 ms67.296 ms65.587 ms70.763 ms4750.0000250.0000-12,897 KB
Parse12345678901(...)01234567890 [180000]103.943 ms2.4616 ms2.8347 ms103.285 ms100.249 ms109.843 ms6500.0000500.0000-18,962 KB
Parse12345678901(...)01234567890 [200000]136.343 ms3.9093 ms4.0146 ms135.607 ms131.575 ms148.198 ms9500.0000500.0000-28,922 KB
Parse12345678901(...)01234567890 [220000]154.695 ms3.6123 ms3.7096 ms154.182 ms149.801 ms163.375 ms17500.0000500.0000-40,999 KB
Parse12345678901(...)01234567890 [240000]184.792 ms20.8938 ms24.0613 ms173.449 ms163.513 ms234.269 ms16000.00001000.0000-39,244 KB
Parse12345678901(...)01234567890 [260000]160.434 ms3.0439 ms3.5053 ms160.026 ms155.602 ms167.424 ms12500.0000500.0000-33,322 KB
Parse12345678901(...)01234567890 [280000]134.838 ms2.4207 ms2.2644 ms134.531 ms132.314 ms139.587 ms9500.0000500.0000-22,696 KB
Parse12345678901(...)01234567890 [300000]140.690 ms2.4686 ms2.4245 ms141.285 ms134.758 ms144.021 ms7500.00001500.00001000.000024,318 KB
Author:key-moon
Assignees:-
Labels:

area-System.Numerics

Milestone:-

@dnfadmin

dnfadmin commented Apr 27, 2021

Copy link
Copy Markdown

CLA assistant check
All CLA requirements met.

@jeffhandley

Copy link
Copy Markdown
Member

@pgovind Could you review this when you get a chance please?

@pgovind

Copy link
Copy Markdown

Would you mind rebasing this PR on the latest main? I spent a little bit of time looking at the PR today and the changes from #47842 making it hard to discern this PR's changes.

@key-moon

Copy link
Copy Markdown
ContributorAuthor

Thanks for reviewing. I rebased onto current main branch. Since it requires history modification, I force-pushed the change. Sorry for inconvenience.

}
else
{
if (numberScale < 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just adding a note to myself: This is the real change in the PR. The block above is just refactoring inside an if. I'll review this PR this week. It's taking some time since I need to go through the algorithm first and then review the implementation here.

}
}

private static int s_naiveThreshold = 20000;

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.

Nit:

privateconstintNaiveThreshold=20_000;

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.

Also, it'd be helpful to add a comment about how this value was selected.

Comment threadsrc/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs Outdated
Co-authored-by: Stephen Toub <stoub@microsoft.com>
@jeffhandley

Copy link
Copy Markdown
Member

@key-moon and @dotnet/area-system-numerics, I just realized that this PR was still open, but the originating branch has been deleted. I'm going to close the PR, but if you want to proceed with it, we could still carry forward starting with the last commit.

@key-moon

Copy link
Copy Markdown
ContributorAuthor

I accidentally deleted a forked repository yesterday. I want to continue from the last commit. What do I need to do? I apologize for the inconvenience caused.

@jeffhandley

Copy link
Copy Markdown
Member

No problem; I'm sorry we let the PR sit as long as we did.

I tried to see if I could recover the commit, but I'm not sure if that will work. I think the best bet here is to create a new branch and reapply the changes that are still shown in this PRs files changed tab. Since the changes look pretty well-contained, I think that'll be easier than trying to pull off some GitHub/git magic to recover the actual previous commits. From there, you can create a new PR and link to this one in the new PR description.

@ghostghost locked as resolved and limited conversation to collaborators Aug 2, 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.

5 participants

@key-moon@dnfadmin@jeffhandley@pgovind@stephentoub