Skip to content

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Oct 1, 2021

A tiny improvement for DateTime.Equals:

bool Test1(DateTime d1, DateTime d2) => d1 == d2;

// cmp against a constant
bool Test2(DateTime d1, DateTime d2) => d1 == new DateTime(637686893321649705);

Codegen diff: https://www.diffchecker.com/v4BfSbey
Could be done in JIT but it needs forward-sub feature for that first.

Self-contained benchmark: https://gist.github.com/EgorBo/e5642d9c2d362bb53880d3c9c87e5b95


|         Method |         d1 |         d2 |     Mean |    Error |   StdDev |
|--------------- |----------- |----------- |---------:|---------:|---------:|
|     Equals_new | MyDt[1000] | MyDt[1000] | 599.9 ns |  0.46 ns |  0.38 ns |
|     Equals_old | MyDt[1000] | MyDt[1000] | 651.8 ns |  0.70 ns |  0.62 ns |

|     Equals_new | MyDt[1000] | MyDt[1000] | 806.2 ns | 15.59 ns | 16.68 ns |
|     Equals_old | MyDt[1000] | MyDt[1000] | 859.2 ns |  2.34 ns |  2.19 ns |

| Equals_new_cns | MyDt[1000] | MyDt[1000] | 436.3 ns |  0.90 ns |  0.75 ns |
| Equals_old_cns | MyDt[1000] | MyDt[1000] | 855.7 ns | 13.78 ns | 12.89 ns |

| Equals_new_cns | MyDt[1000] | MyDt[1000] | 436.0 ns |  2.50 ns |  2.22 ns |
| Equals_old_cns | MyDt[1000] | MyDt[1000] | 875.0 ns | 16.63 ns | 17.80 ns |

@ghost
Copy link

ghost commented Oct 1, 2021

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@ghost
Copy link

ghost commented Oct 1, 2021

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

Issue Details

A tiny improvement for DateTime.Equals:

bool Test1(DateTime d1, DateTime d2) => d1 == d2;

// cmp against a constant
bool Test2(DateTime d1, DateTime d2) => d1 == new DateTime(637686893321649705);

Codegen diff: https://www.diffchecker.com/v4BfSbey
Could be done in JIT but it needs forward-sub feature for that first.

Self-contained benchmark: https://gist.github.com/EgorBo/e5642d9c2d362bb53880d3c9c87e5b95


|         Method |         d1 |         d2 |     Mean |    Error |   StdDev |
|--------------- |----------- |----------- |---------:|---------:|---------:|
|     Equals_new | MyDt[1000] | MyDt[1000] | 599.9 ns |  0.46 ns |  0.38 ns |
|     Equals_old | MyDt[1000] | MyDt[1000] | 651.8 ns |  0.70 ns |  0.62 ns |

|     Equals_new | MyDt[1000] | MyDt[1000] | 806.2 ns | 15.59 ns | 16.68 ns |
|     Equals_old | MyDt[1000] | MyDt[1000] | 859.2 ns |  2.34 ns |  2.19 ns |

| Equals_new_cns | MyDt[1000] | MyDt[1000] | 436.3 ns |  0.90 ns |  0.75 ns |
| Equals_old_cns | MyDt[1000] | MyDt[1000] | 855.7 ns | 13.78 ns | 12.89 ns |

| Equals_new_cns | MyDt[1000] | MyDt[1000] | 436.0 ns |  2.50 ns |  2.22 ns |
| Equals_old_cns | MyDt[1000] | MyDt[1000] | 875.0 ns | 16.63 ns | 17.80 ns |
Author: EgorBo
Assignees: -
Labels:

area-System.Runtime

Milestone: -

public static bool Equals(DateTime t1, DateTime t2)
{
return t1.Ticks == t2.Ticks;
return t1 == t2;
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we have similar pattern in TimeOnly class:

public bool Equals(TimeOnly value) => _ticks == value._ticks;

Copy link
Member Author

@EgorBo EgorBo Oct 1, 2021

Choose a reason for hiding this comment

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

TimeOnly's one compares two fields so it's ok. DateTime compared two computed properties

@jkotas
Copy link
Member

jkotas commented Oct 1, 2021

What does it do to perf on 32-bit platforms? (I think it is ok to take this even it makes 32-bit slower, but it would be useful to have it as a datapoint.)

@jkotas
Copy link
Member

jkotas commented Oct 1, 2021

Just a guess - masking the top two bits instead of shifting may be significantly faster on 32-bit, and about the same on 64-bit.

@EgorBo
Copy link
Member Author

EgorBo commented Oct 1, 2021

What does it do to perf on 32-bit platforms? (I think it is ok to take this even it makes 32-bit slower, but it would be useful to have it as a datapoint.)

It seems it's still faster on 32bit, here is the codegen diff: https://www.diffchecker.com/vexK8vcb

|         Method |       Job |              Toolchain | IterationCount | LaunchCount | WarmupCount |         d1 |         d2 |    eq |       Mean |    Error |   StdDev |
|--------------- |---------- |----------------------- |--------------- |------------ |------------ |----------- |----------- |------ |-----------:|---------:|---------:|

// equals always return false
|     Equals_new | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] | False | 1,242.1 ns |  2.13 ns |  1.67 ns |
|     Equals_old | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] | False | 1,426.8 ns | 11.18 ns | 10.46 ns |

| Equals_new_cns | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] | False |   750.8 ns |  7.30 ns |  6.83 ns |
| Equals_old_cns | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] | False |   861.4 ns |  1.02 ns |  0.85 ns |


// equals always return true
|     Equals_new | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] |  True | 1,409.0 ns |  3.83 ns |  3.40 ns |
|     Equals_old | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] |  True | 1,605.4 ns | 13.36 ns | 11.16 ns |

| Equals_new_cns | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] |  True |   768.4 ns |  1.91 ns |  1.79 ns |
| Equals_old_cns | InProcess | InProcessEmitToolchain |        Default |     Default |     Default | MyDt[1000] | MyDt[1000] |  True |   861.5 ns |  1.81 ns |  1.60 ns |

Just a guess - masking the top two bits instead of shifting may be significantly faster on 32-bit, and about the same on 64-bit.

Looks like it hurts x64 because << 2 2 is contained and for the mask we have to request an additional reg. Diff for Equals_new_cns on x64: https://www.diffchecker.com/1pJUMY6t (<<2 is on the left)

@kunalspathak
Copy link
Contributor

Self-contained benchmark: https://gist.github.com/EgorBo/e5642d9c2d362bb53880d3c9c87e5b95

Can you add this to dotnet/performance ?

@EgorBo
Copy link
Member Author

EgorBo commented Oct 1, 2021

Self-contained benchmark: https://gist.github.com/EgorBo/e5642d9c2d362bb53880d3c9c87e5b95

Can you add this to dotnet/performance ?

Sure, it seems we don't cover these APIs there

@EgorBo EgorBo merged commit 2832723 into dotnet:main Oct 2, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants