Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Adding some basic perf tests for Double.TryParse and Single.TryParse #32392

Merged
merged 1 commit into from
Sep 21, 2018
Merged

Adding some basic perf tests for Double.TryParse and Single.TryParse #32392

merged 1 commit into from
Sep 21, 2018

Conversation

tannergooding
Copy link
Member

CC. @adamsitnik, @eerhardt, @danmosemsft, @jkotas

@danmoseley
Copy link
Member

danmoseley commented Sep 21, 2018

Do we need numbers separately for each of these? Do I need to report separately on negative pi and negative e?

If we had single test for Double.TryParse (mixing up all the test values you have) with a single result, that is probably sufficient regression protection?

Another possibility is two or three tests, for the different major codepaths (or duration buckets)

@tannergooding
Copy link
Member Author

I just wrote this using the existing format the other tests follow.

I've sent a separate e-mail asking about what we can do for non micro-benchmarking. As I believe it would be good to get numbers for broader ranges of inputs here.

@tannergooding
Copy link
Member Author

Numbers are:

   System.Runtime.Performance.Tests.dll                                                                                                             | Metric   | Unit | Iterations |    Average |    STDEV.S |        Min |      Max
  :------------------------------------------------------------------------------------------------------------------------------------------------ |:-------- |:----:|:----------:| ----------:| ----------:| ----------:| --------:
   System.Tests.Perf_Double.DefaultTryParse(input: "0", innerIterations: 10000000)                                                                  | Duration | msec |     18     |    573.496 |      5.463 |    566.160 |  585.889
   System.Tests.Perf_Double.DefaultTryParse(input: "-0.0", innerIterations: 10000000)                                                               | Duration | msec |     16     |    626.403 |      4.114 |    620.104 |  636.045
   System.Tests.Perf_Double.DefaultTryParse(input: "1", innerIterations: 1000000)                                                                   | Duration | msec |    158     |     63.624 |      3.134 |     61.598 |   89.167
   System.Tests.Perf_Double.DefaultTryParse(input: "-1", innerIterations: 1000000)                                                                  | Duration | msec |    150     |     66.712 |      2.061 |     64.528 |   83.712
   System.Tests.Perf_Double.DefaultTryParse(input: "1.7976931348623157E+308", innerIterations: 100000)                                              | Duration | msec |    863     |     11.594 |      0.479 |     11.033 |   15.891
   System.Tests.Perf_Double.DefaultTryParse(input: "-1.7976931348623157E+308", innerIterations: 100000)                                             | Duration | msec |    776     |     12.890 |      1.045 |     11.703 |   22.712
   System.Tests.Perf_Double.DefaultTryParse(input: "2.2250738585072009E-308", innerIterations: 100000)                                              | Duration | msec |    805     |     12.421 |      0.531 |     11.805 |   18.015
   System.Tests.Perf_Double.DefaultTryParse(input: "-2.2250738585072009E-308", innerIterations: 100000)                                             | Duration | msec |    777     |     12.867 |      0.841 |     12.156 |   24.272
   System.Tests.Perf_Double.DefaultTryParse(input: "2.2250738585072014E-308", innerIterations: 100000)                                              | Duration | msec |    845     |     11.839 |      0.536 |     11.271 |   18.168
   System.Tests.Perf_Double.DefaultTryParse(input: "-2.2250738585072014E-308", innerIterations: 100000)                                             | Duration | msec |    827     |     12.096 |      0.389 |     11.574 |   15.560
   System.Tests.Perf_Double.DefaultTryParse(input: "2.7182818284590451", innerIterations: 1000000)                                                  | Duration | msec |     83     |    120.979 |      7.147 |    108.890 |  148.788
   System.Tests.Perf_Double.DefaultTryParse(input: "-2.7182818284590451", innerIterations: 1000000)                                                 | Duration | msec |     89     |    113.226 |      3.905 |    107.618 |  128.201
   System.Tests.Perf_Double.DefaultTryParse(input: "3.1415926535897931", innerIterations: 1000000)                                                  | Duration | msec |     94     |    106.495 |      3.989 |    103.279 |  125.024
   System.Tests.Perf_Double.DefaultTryParse(input: "-3.1415926535897931", innerIterations: 1000000)                                                 | Duration | msec |     93     |    108.178 |      1.864 |    106.315 |  120.034
   System.Tests.Perf_Double.DefaultTryParse(input: "4.94065645841247E-324", innerIterations: 100000)                                                | Duration | msec |    818     |     12.225 |      2.342 |     10.925 |   32.472
   System.Tests.Perf_Double.DefaultTryParse(input: "-4.94065645841247E-324", innerIterations: 100000)                                               | Duration | msec |    812     |     12.315 |      2.246 |     11.242 |   38.022
   System.Tests.Perf_Double.DefaultTryParse(input: "∞", innerIterations: 10000000)                                                                  | Duration | msec |     15     |    666.788 |     40.956 |    636.229 |  797.292
   System.Tests.Perf_Double.DefaultTryParse(input: "-∞", innerIterations: 10000000)                                                                 | Duration | msec |     16     |    632.763 |      4.723 |    627.281 |  643.321
   System.Tests.Perf_Double.DefaultTryParse(input: "NaN", innerIterations: 10000000)                                                                | Duration | msec |     18     |    562.406 |      5.361 |    555.495 |  574.292
   System.Tests.Perf_Single.DefaultTryParse(input: "0", innerIterations: 10000000)                                                                  | Duration | msec |     17     |    615.989 |     46.763 |    563.941 |  751.326
   System.Tests.Perf_Single.DefaultTryParse(input: "-0.0", innerIterations: 10000000)                                                               | Duration | msec |     16     |    645.765 |     36.646 |    619.597 |  751.613
   System.Tests.Perf_Single.DefaultTryParse(input: "1", innerIterations: 1000000)                                                                   | Duration | msec |    151     |     66.247 |      7.834 |     59.540 |  104.543
   System.Tests.Perf_Single.DefaultTryParse(input: "-1", innerIterations: 1000000)                                                                  | Duration | msec |    142     |     70.783 |      6.694 |     63.987 |  101.003
   System.Tests.Perf_Single.DefaultTryParse(input: "1.17549421E-38", innerIterations: 100000)                                                       | Duration | msec |    1000    |      9.715 |      1.171 |      8.883 |   41.561
   System.Tests.Perf_Single.DefaultTryParse(input: "-1.17549421E-38", innerIterations: 100000)                                                      | Duration | msec |    1000    |      9.827 |      0.533 |      9.298 |   16.759
   System.Tests.Perf_Single.DefaultTryParse(input: "1.17549435E-38", innerIterations: 100000)                                                       | Duration | msec |    986     |     10.135 |      1.310 |      9.093 |   21.423
   System.Tests.Perf_Single.DefaultTryParse(input: "-1.17549435E-38", innerIterations: 100000)                                                      | Duration | msec |    1000    |      9.780 |      0.653 |      9.232 |   17.538
   System.Tests.Perf_Single.DefaultTryParse(input: "1.401298E-45", innerIterations: 100000)                                                         | Duration | msec |    1000    |      9.373 |      0.931 |      8.583 |   18.102
   System.Tests.Perf_Single.DefaultTryParse(input: "-1.401298E-45", innerIterations: 100000)                                                        | Duration | msec |    1000    |      9.528 |      0.655 |      8.887 |   21.880
   System.Tests.Perf_Single.DefaultTryParse(input: "2.71828175", innerIterations: 1000000)                                                          | Duration | msec |    116     |     86.756 |      3.454 |     82.339 |  104.550
   System.Tests.Perf_Single.DefaultTryParse(input: "-2.71828175", innerIterations: 1000000)                                                         | Duration | msec |    106     |     94.820 |      8.288 |     85.799 |  131.644
   System.Tests.Perf_Single.DefaultTryParse(input: "3.14159274", innerIterations: 1000000)                                                          | Duration | msec |    108     |     92.847 |      7.307 |     83.224 |  120.448
   System.Tests.Perf_Single.DefaultTryParse(input: "-3.14159274", innerIterations: 1000000)                                                         | Duration | msec |    115     |     87.533 |      3.175 |     84.733 |  106.748
   System.Tests.Perf_Single.DefaultTryParse(input: "3.40282347E+38", innerIterations: 100000)                                                       | Duration | msec |    1000    |      9.438 |      0.413 |      8.837 |   13.734
   System.Tests.Perf_Single.DefaultTryParse(input: "-3.40282347E+38", innerIterations: 100000)                                                      | Duration | msec |    1000    |      9.893 |      0.430 |      9.370 |   14.222
   System.Tests.Perf_Single.DefaultTryParse(input: "∞", innerIterations: 10000000)                                                                  | Duration | msec |     16     |    664.412 |     27.762 |    634.765 |  733.712
   System.Tests.Perf_Single.DefaultTryParse(input: "-∞", innerIterations: 10000000)                                                                 | Duration | msec |     16     |    649.574 |     19.826 |    624.734 |  708.976
   System.Tests.Perf_Single.DefaultTryParse(input: "NaN", innerIterations: 10000000)                                                                | Duration | msec |     18     |    563.217 |     13.470 |    547.281 |  594.361

@danmoseley
Copy link
Member

Is it surprising that "1" is 10x slower than "0" ?

@tannergooding
Copy link
Member Author

No, "0" is currently fast-path'd, while "1" is not.

@tannergooding
Copy link
Member Author

Also, I don't think it's 10x slower. The inner iteration count between the two is different. If you scaled it, it would be 180 iterations at 57.350 seconds average vs 158 iterations at 63.624 seconds average.

@danmoseley
Copy link
Member

OK

@@ -49,6 +50,40 @@ public void DefaultToString(double number, int innerIterations)
}
}

[Benchmark]
[InlineData("-∞", 10_000_000)] // Negative Infinity
[InlineData("-1.7976931348623157E+308", 100_000)] // Min Negative Normal
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure how much it matters, but do we want numbers that are larger than 10?

Copy link
Member Author

Choose a reason for hiding this comment

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

That shouldn't matter as much. The current algorithm is mostly dependent on the length of the input string rather than the actual magnitude of its value.

{
for (int i = 0; i < innerIterations; i++)
{
_bool = double.TryParse(input, out var result);
Copy link
Member

Choose a reason for hiding this comment

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

Do we want (or would it be valuable) to test TryParse that takes a Span<char>?

Copy link
Member Author

Choose a reason for hiding this comment

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

double.TryParse(string, out double) is already calling the Span overload internally: https://source.dot.net/#System.Private.CoreLib/shared/System/Double.cs,315

[Benchmark]
[InlineData("-∞", 10_000_000)] // Negative Infinity
[InlineData("-1.7976931348623157E+308", 100_000)] // Min Negative Normal
[InlineData("-3.1415926535897931", 1_000_000)] // Negative pi
Copy link
Member

Choose a reason for hiding this comment

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

How about numbers with commas (thousands separator)?

Copy link
Member

Choose a reason for hiding this comment

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

Also - numbers with white space at the beginning/end. from https://docs.microsoft.com/en-us/dotnet/api/system.double.tryparse

or a string of the form:

[ws][sign][integral-digits,]integral-digits[.[fractional-digits]][e[sign]exponential-digits][ws]

In reply to: 219635010 [](ancestors = 219635010)

Copy link
Member Author

Choose a reason for hiding this comment

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

These few tests are already adding on 6min 20sec onto the total benchmark time (38 new tests at 10s each). I think there are a number of other scenarios that would be good to test, but the current Benchmark.NET setup does not make that very feasible, so I tried to cover a number of useful/interesting inputs for the most common string format.

Copy link
Member

@eerhardt eerhardt Sep 21, 2018

Choose a reason for hiding this comment

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

so I tried to cover a number of useful/interesting inputs for the most common string format.

From a cursory look, I think some of your existing ones can be eliminated. For example:

System.Tests.Perf_Single.DefaultTryParse(input: "2.71828175", innerIterations: 1000000)                                                          | Duration | msec |    116     |     86.756 |      3.454 |     82.339 |  104.550
System.Tests.Perf_Single.DefaultTryParse(input: "-2.71828175", innerIterations: 1000000)                                                         | Duration | msec |    106     |     94.820 |      8.288 |     85.799 |  131.644
System.Tests.Perf_Single.DefaultTryParse(input: "3.14159274", innerIterations: 1000000)                                                          | Duration | msec |    108     |     92.847 |      7.307 |     83.224 |  120.448
System.Tests.Perf_Single.DefaultTryParse(input: "-3.14159274", innerIterations: 1000000)                                                         | Duration | msec |    115     |     87.533 |      3.175 |     84.733 |  106.748

Yes, these are important numbers: e and pi, but is parsing these numbers really that much different of a scenario?

Similar comment for:

   System.Tests.Perf_Double.DefaultTryParse(input: "1.7976931348623157E+308", innerIterations: 100000)                                              | Duration | msec |    863     |     11.594 |      0.479 |     11.033 |   15.891
   System.Tests.Perf_Double.DefaultTryParse(input: "-1.7976931348623157E+308", innerIterations: 100000)                                             | Duration | msec |    776     |     12.890 |      1.045 |     11.703 |   22.712
   System.Tests.Perf_Double.DefaultTryParse(input: "2.2250738585072009E-308", innerIterations: 100000)                                              | Duration | msec |    805     |     12.421 |      0.531 |     11.805 |   18.015
   System.Tests.Perf_Double.DefaultTryParse(input: "-2.2250738585072009E-308", innerIterations: 100000)                                             | Duration | msec |    777     |     12.867 |      0.841 |     12.156 |   24.272
   System.Tests.Perf_Double.DefaultTryParse(input: "2.2250738585072014E-308", innerIterations: 100000)                                              | Duration | msec |    845     |     11.839 |      0.536 |     11.271 |   18.168
   System.Tests.Perf_Double.DefaultTryParse(input: "-2.2250738585072014E-308", innerIterations: 100000)                                             | Duration | msec |    827     |     12.096 |      0.389 |     11.574 |   15.560

I think we could probably drop 2-3 from each of those blocks, and add at least 1 scenario with a comma in it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Talked with @eerhardt briefly offline.

I definitely agree that we should add more tests, but I am waiting to hear back on if we have or plan to have a better story for "end to end" benchmarks (so that we can actually validate the average perf for a broad range of inputs, or for benchmarks that take longer than 1 second per outer iteration to run).

The current tests where chosen because they have the broadest impact on the current algorithm (overall) and will be the most useful in validating some related changes going in.

@ViktorHofer
Copy link
Member

This broke the perf CI leg: https://ci2.dot.net/job/dotnet_corefx/job/perf/job/master/job/perf_windows_nt_release/6797/consoleText

 [9/24/2018 12:37:51 PM][INF] File saved to: "C:\j\w\perf_windows_---356c2fc4\bin\tests\System.Runtime.Performance.Tests\netcoreapp-Windows_NT-Release-x64\Perf-Profile-System.Runtime.Performance.Tests.csv"
  [2018-09-24 12:37][ERROR] Unexpected exception caught: Number of values for DefaultToString(number: 2.2250738585072E-308, innerIterations: 100000) exceeded 128
  [2018-09-24 12:37][ERROR] Traceback (most recent call last):
    File "C:\j\w\perf_windows_---356c2fc4\Tools/Microsoft.BenchView.JSONFormat\tools/measurement.py", line 302, in main
      merge_tests(tests, infile, args)
    File "C:\j\w\perf_windows_---356c2fc4\Tools/Microsoft.BenchView.JSONFormat\tools/measurement.py", line 284, in merge_tests
      insert_test_values(test, value, metric, drop_value)
    File "C:\j\w\perf_windows_---356c2fc4\Tools/Microsoft.BenchView.JSONFormat\tools/measurement.py", line 250, in insert_test_values
      test.name, MAX_NUMBER_VALUES))
  OverflowError: Number of values for DefaultToString(number: 2.2250738585072E-308, innerIterations: 100000) exceeded 128

cc @jorive

@ViktorHofer
Copy link
Member

Probably a xunit-performance bug as the remaining digits are truncated.

code:

[InlineData(1.17549421E-38f, 100_000)]              // Max Positive Subnormal
[InlineData(1.17549435E-38f, 100_000)]              // Min Positive Normal 

report:

System.Tests.Perf_Single.DefaultToString(number: 1.175494E-38, innerIterations: 100000) 
System.Tests.Perf_Single.DefaultToString(number: 1.175494E-38, innerIterations: 100000)

@tannergooding
Copy link
Member Author

Logged xunit/xunit#1822

@karelz karelz added this to the 3.0 milestone Oct 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants