-
Notifications
You must be signed in to change notification settings - Fork 731
Added caching to improve equality strategy calculation #1719
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added caching to improve equality strategy calculation #1719
Conversation
|
Gave it a spin.
Relative and absolute differences compared to before #1708
|
|
Thanks. I still have to do that myself when I'm back on my dev machine. The way I read this is that on .NET 4.7, the dictionary adds a little bit of overhead, but with more tests and more depth, it becomes faster. But I was hoping to get a larger improvement here. How did you build that comparison chart? |
|
A potential bias with this benchmark is that it favors adding caches of types, as it reuses I manually created the comparison chart using a spreadsheet. |
3fafe22 to
26dbbaf
Compare
|
To my surprise, first checking the size of the |
26dbbaf to
b5ea2a4
Compare
jnyrup
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concern
Since we're about to cache EqualityStrategy.
Are we sure that one cannot in any (reasonable) way modify any of referenceTypes, valueTypes, compareRecordsByValue or getDefaultEqualityStrategy?
E.g. this code compiles and while strategy2 should be different from strategy we have already cached an equality strategy for IEnumerable.
var options = new EquivalencyAssertionOptions();
EqualityStrategy strategy = options.As<IEquivalencyAssertionOptions>().GetEqualityStrategy(typeof(IEnumerable));
options.ComparingByMembers<IEnumerable>();
EqualityStrategy strategy2 = options.As<IEquivalencyAssertionOptions>().GetEqualityStrategy(typeof(IEnumerable));This was discovered by adding static to the lambdas for GetOrdAdd to see if they captured any state.
Benchmarks
| Runtime | N | Depth | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| .NET 5.0 | 1 | 1 | 37.05 us | 0.306 us | 0.271 us | 7.2021 | 0.1831 | - | 44 KB |
| .NET Framework 4.7.2 | 1 | 1 | 53.30 us | 0.726 us | 0.643 us | 7.5073 | 0.1831 | - | 46 KB |
| .NET 5.0 | 1 | 2 | 46.12 us | 0.355 us | 0.315 us | 9.3384 | 0.2441 | - | 58 KB |
| .NET Framework 4.7.2 | 1 | 2 | 65.97 us | 0.619 us | 0.549 us | 9.7656 | 0.2441 | - | 60 KB |
| .NET 5.0 | 1 | 6 | 85.83 us | 0.568 us | 0.531 us | 18.1885 | 0.6104 | - | 112 KB |
| .NET Framework 4.7.2 | 1 | 6 | 112.31 us | 1.475 us | 1.307 us | 19.0430 | 0.6104 | - | 117 KB |
| .NET 5.0 | 10 | 1 | 224.41 us | 1.134 us | 1.061 us | 49.3164 | 1.7090 | - | 303 KB |
| .NET Framework 4.7.2 | 10 | 1 | 296.11 us | 3.825 us | 3.578 us | 51.7578 | 1.4648 | - | 318 KB |
| .NET 5.0 | 10 | 2 | 321.93 us | 2.029 us | 1.898 us | 70.8008 | 2.9297 | - | 434 KB |
| .NET Framework 4.7.2 | 10 | 2 | 415.42 us | 4.357 us | 3.862 us | 74.2188 | 2.9297 | - | 457 KB |
| .NET 5.0 | 10 | 6 | 731.81 us | 4.058 us | 3.796 us | 158.2031 | 9.7656 | - | 974 KB |
| .NET Framework 4.7.2 | 10 | 6 | 968.98 us | 19.304 us | 32.253 us | 166.0156 | 8.7891 | - | 1,025 KB |
| .NET 5.0 | 100 | 1 | 2,165.82 us | 11.930 us | 11.159 us | 472.6563 | 54.6875 | - | 2,911 KB |
| .NET Framework 4.7.2 | 100 | 1 | 2,713.54 us | 33.405 us | 31.247 us | 496.0938 | 50.7813 | - | 3,050 KB |
| .NET 5.0 | 100 | 2 | 3,120.49 us | 10.969 us | 9.723 us | 691.4063 | 97.6563 | - | 4,239 KB |
| .NET Framework 4.7.2 | 100 | 2 | 3,961.89 us | 40.942 us | 31.965 us | 718.7500 | 93.7500 | - | 4,446 KB |
| .NET 5.0 | 100 | 6 | 6,904.14 us | 56.297 us | 52.660 us | 1578.1250 | 289.0625 | - | 9,677 KB |
| .NET Framework 4.7.2 | 100 | 6 | 8,946.78 us | 98.731 us | 87.523 us | 1640.6250 | 281.2500 | 15.6250 | 10,170 KB |
| .NET 5.0 | 500 | 1 | 10,518.87 us | 43.500 us | 40.690 us | 2359.3750 | 46.8750 | - | 14,497 KB |
| .NET Framework 4.7.2 | 500 | 1 | 14,044.45 us | 122.977 us | 115.033 us | 2468.7500 | 31.2500 | - | 15,184 KB |
| .NET 5.0 | 500 | 2 | 15,943.30 us | 107.601 us | 100.650 us | 3437.5000 | 62.5000 | - | 21,140 KB |
| .NET Framework 4.7.2 | 500 | 2 | 19,830.24 us | 128.631 us | 120.322 us | 3593.7500 | 62.5000 | - | 22,165 KB |
| .NET 5.0 | 500 | 6 | 36,714.90 us | 211.049 us | 197.415 us | 7857.1429 | 357.1429 | - | 48,341 KB |
| .NET Framework 4.7.2 | 500 | 6 | 44,841.02 us | 151.675 us | 134.456 us | 8250.0000 | 333.3333 | 83.3333 | 50,793 KB |
| Runtime | N | Depth | Time rel | Time abs | GC rel | GC abs |
|---|---|---|---|---|---|---|
| .NET 5.0 | 1 | 1 | 0.83 | -7.39 us | 0.81 | -10 KB |
| .NET Framework 4.7.2 | 1 | 1 | 0.90 | -6.03 us | 0.78 | -13 KB |
| .NET 5.0 | 1 | 2 | 0.78 | -13.17 us | 0.79 | -15 KB |
| .NET Framework 4.7.2 | 1 | 2 | 0.86 | -10.32 us | 0.76 | -19 KB |
| .NET 5.0 | 1 | 6 | 0.73 | -31.29 us | 0.75 | -37 KB |
| .NET Framework 4.7.2 | 1 | 6 | 0.76 | -34.63 us | 0.71 | -47 KB |
| .NET 5.0 | 10 | 1 | 0.72 | -86.91 us | 0.74 | -107 KB |
| .NET Framework 4.7.2 | 10 | 1 | 0.76 | -94.07 us | 0.71 | -133 KB |
| .NET 5.0 | 10 | 2 | 0.73 | -121.46 us | 0.73 | -161 KB |
| .NET Framework 4.7.2 | 10 | 2 | 0.75 | -140.30 us | 0.70 | -200 KB |
| .NET 5.0 | 10 | 6 | 0.73 | -264.34 us | 0.72 | -379 KB |
| .NET Framework 4.7.2 | 10 | 6 | 0.74 | -340.50 us | 0.69 | -471 KB |
| .NET 5.0 | 100 | 1 | 0.71 | -885.46 us | 0.73 | -1080 KB |
| .NET Framework 4.7.2 | 100 | 1 | 0.71 | -1093.72 us | 0.69 | -1340 KB |
| .NET 5.0 | 100 | 2 | 0.69 | -1394.36 us | 0.72 | -1623 KB |
| .NET Framework 4.7.2 | 100 | 2 | 0.70 | -1672.85 us | 0.69 | -2015 KB |
| .NET 5.0 | 100 | 6 | 0.66 | -3479.79 us | 0.72 | -3808 KB |
| .NET Framework 4.7.2 | 100 | 6 | 0.70 | -3826.18 us | 0.68 | -4723 KB |
| .NET 5.0 | 500 | 1 | 0.68 | -4893.25 us | 0.73 | -5400 KB |
| .NET Framework 4.7.2 | 500 | 1 | 0.72 | -5422.73 us | 0.69 | -6701 KB |
| .NET 5.0 | 500 | 2 | 0.69 | -7084.81 us | 0.72 | -8120 KB |
| .NET Framework 4.7.2 | 500 | 2 | 0.69 | -9071.07 us | 0.69 | -10079 KB |
| .NET 5.0 | 500 | 6 | 0.69 | -16263.78 us | 0.72 | -19046 KB |
| .NET Framework 4.7.2 | 500 | 6 | 0.70 | -19520.82 us | 0.68 | -23623 KB |
I've thought about that as well, but given how the options are not reconfigured after the actual object graph comparison starts, it should be save. |
b5ea2a4 to
2eb75ff
Compare
|
Here's a valid example that breaks with caching of public class Class1
{
[Fact]
public void RunMeFirst()
{
new MyClass { Value = 1 }.Should().BeEquivalentTo(new MyClass { Value = 1 });
}
}
// Non-parallel tests are typically run after all parallel tests
[Collection("AssertionOptionsSpecs")]
public sealed class Class2 : IDisposable
{
public Class2()
{
AssertionOptions.AssertEquivalencyUsing(opt => opt.ComparingByValue<MyClass>());
}
[Fact]
public void RunMeSecond()
{
new MyClass { Value = 1 }.Should().NotBeEquivalentTo(new MyClass { Value = 1 });
}
public void Dispose()
{
AssertionOptions.AssertEquivalencyUsing(_ => new EquivalencyAssertionOptions());
}
}
public class MyClass
{
public int Value { get; set; }
}
[CollectionDefinition("AssertionOptionsSpecs", DisableParallelization = true)]
public class AssertionOptionsSpecsDefinition { } |
|
And the numbers for leaving out caching
|
Did you test this? I haven't tried myself yet, but I'm wondering how that could fail. The cache is per instance of the |
|
I did and and just double checked. |
2eb75ff to
ebcf4ce
Compare
|
I don't even get how you figured that out. Anyway, I think I've fixed it now. |
Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs
Show resolved
Hide resolved
ebcf4ce to
953d297
Compare
Tries to compensate for the performance regression in #1708