-
Notifications
You must be signed in to change notification settings - Fork 5k
Improve the performance of ImmutableList.Contains #40540
Improve the performance of ImmutableList.Contains #40540
Conversation
IndexOf has a lot of overhead because it's looking for an index when all Contains wants to do is find a matching element.
src/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs
Outdated
Show resolved
Hide resolved
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.
The current iteration looks good to me.
@AArnott, can you share an example where using the enumerator is measurably faster than using the indexer? I'm having trouble creating a collection large enough, but maybe it requires creating one in a special way? |
@stephentoub I don't recall doing the experiment myself. I thought you had tried it and found 1M to be the turning point. So I'm happy to go with a pure-indexer enumerator if you couldn't find a size where that was worse. |
@AArnott @stephentoub
And then with the indexer implementation:
Switching Contains back to use IndexOf (and the Enumerator directly) here are the results (without my changes then with indexer enumerator):
I don't honestly know what to make of this data, with a pure indexer implementation the enumerator for ints seems to take a performance hit while for strings it performs better. None of this matters for the Contains case but it's interesting. |
cc: @stephentoub @AArnott as explained by @shortspider, based on the resulting benchmarks, the numbers seem to not show enough reason for merging. Should this be closed then? |
Actually the numbers shown are by changing the Enumerator to use the indexer... @shortspider do you have numbers for your current change to speed up Overall the change looks good to me, but do you have benchmark numbers for the current change with big and small collections? |
src/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs
Show resolved
Hide resolved
Thanks for the improvement, @shortspider. I tried this out, and it looks like a very nice improvement to throughput.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Running;
using System.Collections.Immutable;
using System.Linq;
[MemoryDiagnoser]
public class Program
{
static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);
private ImmutableList<int> _list;
[Params(1, 10, 100, 1000 )]
public int Length { get; set; }
[GlobalSetup]
public void Setup() => _list = ImmutableList<int>.Empty.AddRange(Enumerable.Range(0, Length));
[Benchmark]
public bool Contains0() => _list.Contains(0); // first element
[Benchmark]
public bool ContainsLengthDiv2() => _list.Contains(Length / 2); // root, or close to it
[Benchmark]
public bool ContainsLength() => _list.Contains(Length); // doesn't exist
} |
@AArnott, I just tried it again. Not sure what I was looking at when I commented before, but I just rewrote the enumerator to just be in terms of indexing, and while it was 2-3x faster for small sizes, starting at somewhere around 5_000 nodes on my current machine it started to get slower. By 100_000 nodes it was close to ~25% slower. So, it does seem like we shouldn't go with a pure-indexer approach. |
* Improve ImmutableList.IndexOf performance * Implement a spicific Contains method IndexOf has a lot of overhead because it's looking for an index when all Contains wants to do is find a matching element. * Fix tests * Revert change to IndexOf Commit migrated from dotnet/corefx@a866d96
Fixes #36407