Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

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

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Refactor some DateTime and TimeSpan formatting/parsing methods - #101640

Merged
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers
May 6, 2024
Merged

Refactor some DateTime and TimeSpan formatting/parsing methods#101640
tarekgh merged 15 commits into
dotnet:mainfrom
lilinus:dt-ts-format-parse-helpers

Conversation

@lilinus

@lilinuslilinus commented Apr 27, 2024

Copy link
Copy Markdown
Contributor
  • Use FormattingHelpers.CountDigits.
  • Use TimeSpanParse.Pow10 in DateTimeFormat.
  • Change fraction variables from long to int.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Apr 27, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@tarekghtarekgh added this to the 9.0.0 milestone Apr 27, 2024
@danmoseley

Copy link
Copy Markdown
Contributor

Are these sufficiently covered in dotnet/performance? Might be worth running appropriate benchmarks there.


Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
_num = (int)(((uint)_num + power / 2) / power);

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.

_num = (int)(((uint)_num + power / 2) / power);

I am not sure if this dividing by 2 and dividing by power will be more performant than just calling Math.Round?
@tannergooding any opinion on that?

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.

constintMaxFractionDigits=7;[Params(123456789)]publicintNum{get;set;}[Params(10)]publicintTotalDigitsCount{get;set;}internalstaticintPow10UpToMaxFractionDigits(intpow){ReadOnlySpan<int>powersOfTen=[1,10,100,1000,10000,100000,1000000,10000000,];Debug.Assert(powersOfTen.Length==MaxFractionDigits+1);returnpowersOfTen[pow];}[Benchmark]publicintMathRound()=>(int)Math.Round((double)Num/Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits),MidpointRounding.AwayFromZero);[Benchmark]publicintIntegerRound(){uintpower=(uint)Pow10UpToMaxFractionDigits(TotalDigitsCount-MaxFractionDigits);return(int)(((uint)Num+power/2)/power);}
MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.8892 ns0.0068 ns0.0061 ns
IntegerRound123456789101.2103 ns0.0044 ns0.0034 ns

Interesting...I suppose floating point math could be much slower than integer.

@tarekghtarekghApr 29, 2024

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 suggest reverting the code back to use Math.Round. It is even more readable and clear the intention of what the code does.

@xtqqczzextqqczzeApr 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Results on arm64 with the span bounds check removed:

BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.204
[Host] : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD

MethodNumTotalDigitsCountMeanErrorStdDev
MathRound123456789100.4477 ns0.0105 ns0.0093 ns
IntegerRound123456789100.1372 ns0.0037 ns0.0032 ns

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.

Results on arm64 with the span bounds check removed:

Can you provide a public API benchmark that shows any noticeable perf difference? IMO, It doesn't make sense to benchmark internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@EgorBo Not sure about real-world benchmarks, but with the suggested change code size (on X64) is 172 bytes vs is 271 bytes.

https://csharp.godbolt.org/z/YcMKW5fza

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.

@lilinus is it possible you run your perf tests again mentioned in #101640 (comment) with @xtqqczze suggestion to get some idea about the perf of this change? Thanks!

I am talking about the change

uintpower=(uint)Pow10UpToMaxFractionDigits(totalDigitsCount-MaxFractionDigits);_num=(int)(((uint)_num+power/2)/power);

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.

Seems like uint division is slightly faster. I assume you want me to push back to that change so I will do it.

| Method | Job | Toolchain | arg | Mean | Error | StdDev | Median | Min | Max | Ratio | Allocated | Alloc Ratio |
|--------------- |----------- |---------------|------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|------:|----------:|------------:|
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.000000001234567 | 215.0 ns | 0.90 ns | 0.84 ns | 215.0 ns | 213.3 ns | 216.7 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.000000001234567 | 154.5 ns | 0.43 ns | 0.36 ns | 154.4 ns | 154.1 ns | 155.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Mth.Round | 00:00:00.000000001234567 | 155.8 ns | 0.40 ns | 0.35 ns | 155.7 ns | 155.1 ns | 156.4 ns | 0.72 | - | NA |
| TimeSpan_Parse | Job-PGUVPC | main | 00:00:00.00001234567 | 156.1 ns | 0.48 ns | 0.43 ns | 155.8 ns | 155.6 ns | 157.0 ns | 1.00 | - | NA |
| TimeSpan_Parse | Job-AKPZDD | uint division | 00:00:00.00001234567 | 147.1 ns | 0.72 ns | 0.68 ns | 146.7 ns | 146.3 ns | 148.4 ns | 0.94 | - | NA |
| TimeSpan_Parse | Job-RERBPQ | Math.Round | 00:00:00.00001234567 | 151.3 ns | 0.53 ns | 0.50 ns | 151.4 ns | 150.2 ns | 151.9 ns | 0.97 | - | NA |

Benchmark source:

public static IEnumerable<object> Values()
{
yield return "00:00:00.00001234567";
yield return "00:00:00.000000001234567";
}
[Benchmark, ArgumentsSource(nameof(Values))]
public TimeSpan TimeSpan_Parse(string arg) => TimeSpan.Parse(arg, CultureInfo.InvariantCulture);

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.

@lilinus looks you already did :-) I am fine with that as tt provides us with a smaller code size and a slightly faster performance as well.

One last ask, please add a comment above that line telling what is does to be easier for anyone reading the code understand what the code is doing.

@xtqqczze thanks for the suggestion!

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.

Comment added 👍

@tarekgh
tarekgh marked this pull request as ready for review May 2, 2024 15:26

@tarekghtarekgh 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.

@lilinus thanks for helping with this change.

@tarekgh

Copy link
Copy Markdown
Member

@lilinus could you please merge the latest change from main to this PR to be able to proceed?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

@tarekgh, done. Thanks for the feedback etc. I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

@tarekgh

Copy link
Copy Markdown
Member

I also tried benchmarking the changes but had trouble with antivirus stopping the runtime.

Is it possible you can temporary disable the antivirus? Or add exclusion to the runtime and test folders to have the antivirus ignore them?

@lilinus

Copy link
Copy Markdown
ContributorAuthor

Managed to get it working. Benchmarked some DateTime and TimeSpan parsing. Results are looking good IMO. Link to benchmarks: here

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 10 (10.0.19044.3086/21H2/November2021Update)
AMD Ryzen 9 4900HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 9.0.0 (9.0.24.17209), X64 RyuJIT AVX2
Job-GLLMPZ : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-AQRKOX : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
| Method | Job | Toolchain | arg1 | arg2 | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 78.58 ns | 1.200 ns | 1.123 ns | 78.43 ns | 76.52 ns | 80.90 ns | 1.00 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.123 | d\\.hh\\.mm\\.ss\\.FFFFF | 64.23 ns | 0.889 ns | 0.831 ns | 64.38 ns | 62.55 ns | 65.61 ns | 0.82 | 0.02 | - | - | NA |
| TimeSpan_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 72.58 ns | 0.264 ns | 0.234 ns | 72.57 ns | 72.30 ns | 73.05 ns | 1.00 | 0.00 | - | - | NA |
| TimeSpan_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 0.00.00.00.12345 | d\\.hh\\.mm\\.ss\\.fffff | 63.92 ns | 0.693 ns | 0.614 ns | 63.78 ns | 62.92 ns | 64.95 ns | 0.88 | 0.01 | - | - | NA |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 213.02 ns | 0.465 ns | 0.388 ns | 212.95 ns | 212.32 ns | 213.69 ns | 1.00 | 0.00 | 0.0337 | 72 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fff | 179.06 ns | 0.303 ns | 0.268 ns | 179.04 ns | 178.52 ns | 179.53 ns | 0.84 | 0.00 | 0.0343 | 72 B | 1.00 |
| DateTime_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 146.01 ns | 0.579 ns | 0.542 ns | 145.84 ns | 145.43 ns | 147.21 ns | 1.00 | 0.01 | 0.0305 | 64 B | 1.00 |
| DateTime_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 01/01/0001 00:00:12 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 109.59 ns | 0.142 ns | 0.126 ns | 109.61 ns | 109.28 ns | 109.79 ns | 0.75 | 0.00 | 0.0304 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 177.14 ns | 0.506 ns | 0.473 ns | 177.02 ns | 176.46 ns | 178.08 ns | 1.00 | 0.00 | 0.0303 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.fffffff | 164.35 ns | 0.436 ns | 0.407 ns | 164.33 ns | 163.68 ns | 165.22 ns | 0.93 | 0.00 | 0.0301 | 64 B | 1.00 |
| TimeSpan_Format | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 184.79 ns | 0.532 ns | 0.497 ns | 184.62 ns | 184.17 ns | 185.89 ns | 1.00 | 0.00 | 0.0300 | 64 B | 1.00 |
| TimeSpan_Format | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 00:00:12.3456789 | d\\.hh\\.mm\\.ss\\.FFFFFFF | 177.78 ns | 0.358 ns | 0.317 ns | 177.76 ns | 177.20 ns | 178.39 ns | 0.96 | 0.00 | 0.0305 | 64 B | 1.00 |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 225.67 ns | 0.378 ns | 0.335 ns | 225.61 ns | 225.21 ns | 226.24 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.123 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.FFFFF | 202.20 ns | 0.250 ns | 0.209 ns | 202.22 ns | 201.87 ns | 202.51 ns | 0.90 | 0.00 | - | - | NA |
| DateTime_Parse | Job-GLLMPZ | \dnmain\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 219.44 ns | 0.367 ns | 0.325 ns | 219.45 ns | 218.83 ns | 220.02 ns | 1.00 | 0.00 | - | - | NA |
| DateTime_Parse | Job-AQRKOX | \dnrt\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\CoreRun.exe | 2024-05-02.21.40.42.12345 | yyyy\\-MM\\-dd\\.HH\\.mm\\.ss\\.fffff | 218.17 ns | 0.361 ns | 0.320 ns | 218.18 ns | 217.31 ns | 218.65 ns | 0.99 | 0.00 | - | - | NA |

…TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@tarekgh
tarekgh merged commit c81eca8 into dotnet:mainMay 6, 2024
michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request May 9, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@lilinus
lilinus deleted the dt-ts-format-parse-helpers branch May 13, 2024 13:01
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#101640)
* Refactor some DateTime and TimeSpan formatting/parsing methods
* Fix assertion in TimeSpanParse.Pow10
* Don't use Unsafe in TimeSpanParse.Pow10
* Revert changes to TimeSpanParse.Pow10
* Revert "Revert changes to TimeSpanParse.Pow10"
This reverts commit 267d5e8.
* Change method name to Pow10UpToMaxFractionDigits
* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction
* Address feedback in TimeSpanParse
* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction
* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction
* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
---------
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 14, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Globalizationcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lilinus@danmoseley@tarekgh@EgorBo@huoyaoyuan@xtqqczze