Skip to content

Commit 3d59010

Browse files
committed
added IndexList.Contains directly as member
This allows direct use of Contains without interface casting (due to explicit f# interface implementation).
1 parent d799cba commit 3d59010

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

src/FSharp.Data.Adaptive/Datastructures/IndexList.fs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,12 +624,16 @@ type IndexList< [<EqualityConditionalOn>] 'T> internal(l : Index, h : Index, con
624624
/// Tries to find the position for the given Index or -1 if the Index does not exist. O(log N)
625625
member x.IndexOf(index : Index) =
626626
MapExt.getIndex index content
627-
627+
628+
/// Returns true if the item is found in the collection.
629+
member x.Contains(item : 'T) =
630+
content |> MapExt.exists (fun _ vi -> DefaultEquality.equals vi item)
631+
628632
interface ICollection<'T> with
629633
member x.Add(v) = raise (NotSupportedException("IndexList cannot be mutated"))
630634
member x.Clear() = raise (NotSupportedException("IndexList cannot be mutated"))
631635
member x.Remove(v) = raise (NotSupportedException("IndexList cannot be mutated"))
632-
member x.Contains(v) = content |> MapExt.exists (fun _ vi -> DefaultEquality.equals vi v)
636+
member x.Contains(v) = x.Contains(v)
633637
member x.CopyTo(arr,i) = x.CopyTo(arr, i)
634638
member x.IsReadOnly = true
635639
member x.Count = x.Count

src/Test/FSharp.Data.Adaptive.Tests/Benchmarks/IndexListBenchmarks.fs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,68 @@ type IndexListBenchmarks() =
241241
res
242242

243243

244+
//| Method | Count | Mean | Error | StdDev | Gen0 | Allocated |
245+
//|------------- |------ |------------:|----------:|----------:|---------:|-----------:|
246+
//| MapExtExists | 100 | 288.51 us | 4.098 us | 3.633 us | 71.7773 | 440.74 KB |
247+
//| SeqContains | 100 | 57.60 us | 0.835 us | 0.781 us | 11.9019 | 73.03 KB |
248+
//| EnumContains | 100 | 282.88 us | 5.443 us | 6.884 us | 81.5430 | 501.34 KB |
249+
//| MapExtExists | 1000 | 2,034.85 us | 39.085 us | 38.387 us | 492.1875 | 3032.6 KB |
250+
//| SeqContains | 1000 | 405.40 us | 4.611 us | 4.313 us | 66.8945 | 411.19 KB |
251+
//| EnumContains | 1000 | 2,024.88 us | 29.268 us | 27.377 us | 558.5938 | 3431.82 KB |
252+
//| MapExtExists | 10000 | 3,495.86 us | 64.921 us | 60.727 us | 812.5000 | 4985.28 KB |
253+
//| SeqContains | 10000 | 690.07 us | 11.743 us | 10.984 us | 112.3047 | 690.75 KB |
254+
//| EnumContains | 10000 | 3,353.69 us | 66.409 us | 93.097 us | 921.8750 | 5664.15 KB |
255+
//
256+
// Insights: Seq.contains is fasted, but only if used in non-generic way, which mean that this need to be done by the application itself
257+
// and not be abstracted byhind a method on IndexList because 'T there is not restricted to "equality".
258+
// MapExt.exists (as currently used) uses slightly less memory as a search via a generic Seq.contains with a struct enumerator.
259+
// -> keep IndexList.Contains implementation as is
260+
261+
[<MemoryDiagnoser; InProcess>]
262+
[<CategoriesColumn; GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)>]
263+
type IndexListContainsBenchmarks() =
264+
265+
[<Params(100, 1000, 10000); DefaultValue>]
266+
val mutable public Count : int
267+
268+
let mutable list = IndexList.empty<_>
269+
270+
[<GlobalSetup>]
271+
member x.Setup() =
272+
let rnd = Random(101)
273+
let stuff = Array.init x.Count (fun i -> rnd.Next(1000))
274+
list <- IndexList.ofArray stuff
275+
276+
277+
[<Benchmark>]
278+
member x.MapExtExists() =
279+
let mutable res = 0
280+
for i in 0..100 do
281+
if list |> IndexList.exists (fun _ vi -> DefaultEquality.equals vi i) then
282+
res <- res ^^^ i
283+
res
284+
285+
[<Benchmark>]
286+
member x.SeqContains() =
287+
let mutable res = 0
288+
for i in 0..100 do
289+
if list |> Seq.contains i then // NOTE: this uses LanguagePrimitives.HashCompare.GenericEqualityIntrinsic (inlined equality) while if generic DefaultEquality.equals (equality comparer) would be used
290+
res <- res ^^^ i
291+
res
292+
293+
let containsEnum (item : 'T) (list : IndexList<'T>) = // NOTE: would only match Seq.contains performance if int is used instead of 'T
294+
let mutable e = list.GetEnumerator()
295+
let mutable found = false
296+
while e.MoveNext() && not found do
297+
if DefaultEquality.equals e.Current item then
298+
//if e.Current = item then // -> gives same performance as DefaultEquality.equals if function is generic
299+
found <- true
300+
found
301+
302+
[<Benchmark>]
303+
member x.EnumContains() =
304+
let mutable res = 0
305+
for i in 0..100 do
306+
if list |> containsEnum i then
307+
res <- res ^^^ i
308+
res

0 commit comments

Comments
 (0)