Conversation
| HashSet<TKey> collection = _hashSet; | ||
| TKey[] notFound = _notFound; | ||
| for (int i = 0; i < notFound.Length; i++) | ||
| result ^= collection.TryGetValue(notFound[i], out _); |
There was a problem hiding this comment.
The CI fails with:
error CS1061: 'HashSet<TKey>' does not contain a definition for 'TryGetValue' and no accessible extension method 'TryGetValue' accepting a first argument of type 'HashSet<TKey>'
The method got introduced int .NET 4.7.2.
When we were starting the performance repo project, we decided to target .NET 4.6.1 mostly to allow users to open the solution file with old VS. Since using netcoreapp3.0 requires VS 2019, most our users most probably already have .NET 4.7.2|4.8 SDK installed.
@DrewScoggins @billwert should we update the micro benchmarks project to target net48 instead of net461?
There was a problem hiding this comment.
@JeffreyZhao could you please use #if !NETFRAMEWORK for benchmarks that call HashSet.TryGetValue? this should fix the failing CI
#if !NETFRAMEWORK
[Benchmark]
...
#endifThere was a problem hiding this comment.
For the long term Adam I am fine with moving the targeting to .NET 4.8.
There was a problem hiding this comment.
Do I still need to add #if !NETFRAMEWORK around TryGetValue benchmarks?
|
Hi @JeffreyZhao thank you for excellent benchmarks and your improvements in Could you please share results of a benchmark where the performance of |
|
I have a question about the CopyAndRemove benchmarks that are being added? Is that a common scenario that we see with user code? If it is than I think the test makes a lot of sense, but if all we are trying to do is measure the time for removing objects from various collection types it seems like we should try and move the copying of the data outside of the timing loop. |
No, I don't think it's a common scenarios. A benchmark is required to be no side-effect (so that BenchmarkDotNet could run it repeatedly to get an accurate result), so we have to make a copy before actual removal from a collection. I believe it's the same reason that currently we don't have a dedicated benchmark for removal operation, instead, we have to use However, we have |
|
The benchmark results for The case for public Dictionary(int capacity, IEqualityComparer<TKey>? comparer)
{
if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
if (capacity > 0) Initialize(capacity);
if (comparer != EqualityComparer<TKey>.Default)
{
_comparer = comparer;
}
if (typeof(TKey) == typeof(string) && _comparer == null)
{
// To start, move off default comparer for string which is randomised
_comparer = (IEqualityComparer<TKey>)NonRandomizedStringEqualityComparer.Default;
}
}In Also I can imagine more cases that hasn't been covered by existing benchmarks. For example, larger structs. We know copying large structs (frequently) hurts performance, but sometimes we would make copies in code naturally (e.g., using temporary variable, which is considered a good code practice in many places), and we need to modify the code as accessing struct array by index repeatedly, or using So I have feelings that using fundamental types like |
|
And regarding the benchmark result. Here's an example of BenchmarkDotNet=v0.12.0, OS=Windows 10.0.17763.805 (1809/October2018Update/Redstone5)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100
[Host] : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
Job-HEYZLQ : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250.0000 ms MaxIterationCount=20
MinIterationCount=15 WarmupCount=1
So |
I agree, it's more a BenchmarkDotNet design limitation |
I think that we should then increase the size of
Increasing the size of |
|
May I ask why BenchmarkDotNet choose to run a benchmark only once if there's a
I think that's a good idea. And what do you think of making the names more obvious, like, |
| result &= copy.Remove(items[i]); | ||
|
|
||
| if (!result) | ||
| throw new InvalidOperationException("All items must be in the collection."); |
There was a problem hiding this comment.
We should not be throwing inside the timing loop. We don't want to be doing any unnecessary work inside of the timing loop, and we shouldn't be doing correctness checks in performance tests. I know that this is a pattern in other parts of the benchmarks, but we should be working to remove those as well.
There was a problem hiding this comment.
May I ask why we shouldn't do that? The check is per iteration instead of per inner-loop of each Remove call so it shouldn't affect the benchmark result. The code is to ensure that the data prepared is what we want, since like RemoveTrue and RemoveFalse are two distinct cases that we want to test, so IMHO the code should be able catch the issue here.
| HashSet<TKey> collection = _hashSet; | ||
| TKey[] notFound = _notFound; | ||
| for (int i = 0; i < notFound.Length; i++) | ||
| result ^= collection.TryGetValue(notFound[i], out _); |
There was a problem hiding this comment.
For the long term Adam I am fine with moving the targeting to .NET 4.8.
I've made some changes to the collection benchmarks when I was working on
HashSetimprovement (dotnet/corefx#40106).Currently there's only one benchmark
CreateAddAndRemoveforHashSet.Remove. The problem ofCreateAddAndRemoveis that it has the “Create" and "Add" part which usually take more time than the "Remove" part so it doesn't like a proper benchmark for removal operations to me.I've created two additional benchmarks
RemoveFalseandCopyAndRemove.RemoveFalsehas no side-affect cause nothing's been removed.CopyAndRemovewould create a copy of collection from constructor and remove all the items from it one by one. Making a self-copy from constructor is much faster than throughAddmethod (for most collections) so the benchmark is more focusing on removal operations.I also introduced two new types
CustomValue(a struct) andCustomObject(a class). I want to make sure the optimizations applied to custom structs (by usingCustomValue) and the current reference generic argumentStringfor collection benchmarks doesn't seem like a good target to me as it has a relatively complex/slow implementation. Those new types have the simplest implementations that meet the minimum requirement of benchmark so the benchmark result would be sorely affected by collection implementation.Those two new types have been applied to collection benchmarks (not limit to new ones mentioned above) as generic arguments.